Pseudo-elements.
::before, ::after, and content.
A pseudo-element is a part of an element that CSS creates virtually — it doesn't exist in the HTML. ::before and ::after are the most used: they add content inside an element without touching the markup. A quote mark, an arrow, a checkmark, a decorative shape — all without a single HTML element. This page covers the five pseudo-elements, the content property that makes them work, and the patterns that actually help.
Short answer: Pseudo-elements style virtual parts of an element. ::before and ::after create content inside an element — they need content to render. ::first-line, ::first-letter, ::selection style existing parts. Use double colons (::before) to distinguish from pseudo-classes (:hover). Use pseudo-elements for decoration only — icons, quote marks, arrows. For meaningful content, use real HTML. Screen readers may or may not announce pseudo-element content.
✅ The five pseudo-elements
Each one styles a different virtual part.
::before
Content inserted before an element's children. Needs content. Uses for icons, quote marks, decorations.
::after
Content inserted after an element's children. Same rules — needs content.
::first-line
Styles the first line of text in a block. Affected by width and line breaks.
::first-letter
Styles the first letter of a block. For drop caps in articles.
::selection
Styles text the user has selected with the cursor. Limited properties (color, background).
✅ Case 1 — Blockquotes
Quote mark without an HTML element.
.quote {
font-style: italic;
padding-left: 1.5rem;
position: relative;
color: #334155;
}
.quote::before {
content: '"';
position: absolute;
left: 0;
top: -0.5rem;
font-size: 3rem;
line-height: 1;
color: #2965f1;
font-family: Georgia, serif;
}
HTML: <p class="quote">The best way to predict the future is to invent it.</p> Rendered: ┌─────────────────────────────┐ │ " The best way to predict │ │ the future is to │ │ invent it. │ └─────────────────────────────┘ ✅ Quote mark is decorative — pure CSS. ✅ No HTML needed. ✅ Changes the quote style by changing one CSS rule. ✅ Screen readers skip the decorative quote. ⚠️ Use <blockquote> for semantic meaning. ::before is only for visual flourish.
✅ Common use cases
Where pseudo-elements earn their keep.
/* 1. External link icon */
a[href^="http"]::after {
content: ' ↗';
font-size: 0.75em;
color: #64748b;
}
/* 2. Required field asterisk */
label.required::after {
content: ' *';
color: red;
}
/* 3. Decorative underline on hover */
.link::after {
content: '';
display: block;
width: 0;
height: 2px;
background: #2965f1;
transition: width 0.2s;
}
.link:hover::after {
width: 100%;
}
/* 4. Selection highlight */
::selection {
background: #2965f1;
color: white;
}
/* 5. Drop cap */
.article > p:first-of-type::first-letter {
font-size: 3.5rem;
font-weight: bold;
float: left;
line-height: 0.9;
padding-right: 0.1em;
color: #2965f1;
}
1. External links get a ↗ icon automatically. 2. Required labels get a red asterisk. 3. Hover underline animates from 0 to 100% width. 4. Selection color matches the brand. 5. First letter of first paragraph is enlarged as a drop cap. All five use cases: decorative, no HTML changes needed. For icons that carry meaning, use real HTML with aria-label or alt text.
✅ The content property
What makes ::before and ::after render.
/* Text */
.badge::before {
content: '★ ';
}
/* Empty string — for shapes and borders */
.shape::before {
content: '';
display: block;
width: 20px;
height: 20px;
background: #2965f1;
border-radius: 50%;
}
/* Special characters */
.breadcrumb a + a::before {
content: ' / ';
color: #94a3b8;
}
/* Attribute values (great for printing) */
@media print {
a[href]::after {
content: ' (' attr(href) ')';
font-size: 0.8em;
}
}
/* Counter (numbered lists without <ol>) */
.section-title {
counter-increment: section;
}
.section-title::before {
content: counter(section) '. ';
color: #2965f1;
font-weight: bold;
}
content takes: ✅ A string: content: '→'; ✅ An empty string: content: ''; ✅ An attribute: content: attr(href); ✅ A counter: content: counter(section); ✅ An image: content: url(icon.svg); But NOT: ❌ HTML elements -> content: '<b>'; // renders as string literals ❌ JavaScript -> content: runCode(); ❌ Dynamic input parameters The content property is for static or CSS-generated layout adjustments only.
Don't use ::before and ::after for content that needs to be accessible. Screen reader support for pseudo-element content is inconsistent — some announce it, some skip it entirely. If the content carries meaning — an error icon, a warning, a count, a status — it must be in the HTML. Use pseudo-elements for decoration only: quote marks, arrows, dividers, decorative shapes. If a sighted user needs to know it, a screen reader user needs to know it too — and CSS-generated content doesn't reliably reach the accessibility tree. For meaningful icons, use <svg aria-label="..."> or a real <span> with visually-hidden text.
⚠️ Common mistakes
The pseudo-element mistakes that cost real time.
contentNo content means no render. Fix: content: '' for shapes, content: 'text' for text.
<img>, <input> — no children. Fix: wrap in a div and style that.
:before works but is legacy. Fix: ::before (double colon).
Screen readers may skip it. Fix: use real HTML for meaning; CSS for decoration.
Defaults to inline — width/height don't apply. Fix: display: block or inline-block.
::before with position: absolute needs a positioned parent. Fix: position: relative on the parent.
::before on replaced elements<video>, <canvas>, <iframe> — no. Fix: wrap.
Screen reader announces twice or not at all. Fix: pick one — usually HTML.
❓ Frequently asked
What are pseudo-elements in CSS?
Pseudo-elements let you style a specific part of an element that isn't a real DOM node. The five main ones: ::before and ::after create virtual content inside an element; ::first-line and ::first-letter style the first line or first letter; ::selection styles the highlighted text. Pseudo-elements use double colons to distinguish them from pseudo-classes. ::before and ::after are the most used — they create decorative content without adding HTML.
What is the content property?
The content property is what makes ::before and ::after show anything. It takes a string, an image URL, a counter, or a few special keywords. Syntax: .quote::before { content: '"'; }. Without content, ::before and ::after don't render. Use content: '' for purely decorative shapes. Common values: content: '"' for quote marks, content: counter(step) for numbered lists.
What is the difference between a pseudo-element and a pseudo-class?
A pseudo-class targets an element in a specific state — :hover, :focus, :first-child. A pseudo-element targets a specific part of an element — ::before, ::after, ::first-line, ::first-letter, ::selection. The syntax is the giveaway: pseudo-classes use one colon (:hover), pseudo-elements use two (::before). Different concepts, different syntax.
Can I use pseudo-elements for accessibility?
Carefully. Content added by ::before and ::after is not always announced by screen readers — some do, some don't. If the content conveys information, it should be in the HTML. Use pseudo-elements for decoration — visual flourishes, borders, shapes — not for meaningful text. If you must use ::before to add text, verify with a screen reader.
Why doesn't ::before/::after show up?
Four common causes. (1) No content property. (2) The parent is display: inline. (3) The parent has no content. (4) Z-index or overflow issue. Fix: add content: '', use display: inline-block on the parent, ensure the parent has content, and check position/z-index/overflow.
When should I use ::before vs adding an element?
Use ::before/::after when the content is purely decorative and doesn't need to be in the DOM. Examples: a quote mark before a blockquote, a decorative arrow, a colored line under a heading. Use a real element when the content is semantic — an icon that conveys meaning, a badge with a number. Rule of thumb: if a screen reader user needs to know it's there, use HTML. If it's visual flourish only, ::before/::after is fine.