Pseudo-elements Before After
::before and ::after generate children for decoration — required content/display, accessibility limits, and practical UI patterns.
- css
- pseudo-elements
::before and ::after create generated children inside an element’s box. They’re ideal for decorative lines, icons, badges, and clearfix-era hacks — not for meaningful content that belongs in HTML.
Docs: MDN ::before, ::after, pseudo-elements.
Minimum viable pseudo-element
.button::before {
content: ""; /* required — without content, box often doesn’t generate */
display: inline-block;
width: 0.5rem;
height: 0.5rem;
margin-inline-end: 0.4rem;
border-radius: 50%;
background: currentColor;
}
Rules of thumb:
contentis required (even"") for::before/::afterto generate a box.- Treat them like children for flex/grid — they become items if the parent is flex/grid.
- Single colon
:beforeis the old CSS2 syntax; prefer::for pseudo-elements.
Decorative patterns
Gradient underline
.link-fancy {
position: relative;
text-decoration: none;
}
.link-fancy::after {
content: "";
position: absolute;
inset-inline: 0;
inset-block-end: -0.15em;
height: 2px;
background: linear-gradient(90deg, var(--brand), transparent);
transform: scaleX(0);
transform-origin: left;
transition: transform 150ms ease;
}
.link-fancy:hover::after,
.link-fancy:focus-visible::after {
transform: scaleX(1);
}
Quote mark
blockquote::before {
content: "“";
font-size: 3rem;
line-height: 1;
color: var(--brand);
float: left;
margin-inline-end: 0.25rem;
}
Prefer real typography/HTML for multilingual quotes when accuracy matters.
Badge count (careful)
.icon-btn[data-count]::after {
content: attr(data-count);
position: absolute;
/* … */
}
attr() in content is fine for decorative counts; screen readers may not announce it as a live status — put the real number in accessible text or aria-label on the control.
Flex/grid participation
.cluster {
display: flex;
align-items: center;
gap: 0.5rem;
}
.cluster::before {
content: "";
width: 4px;
align-self: stretch;
background: var(--brand);
}
Generated boxes count toward layout — surprising gaps and alignment often trace to forgotten pseudo-elements.
What not to put in content
- Critical instructions (“Click save or lose data”)
- Form labels
- SEO text you expect crawlers to treat as primary content
- Interactive hit targets (can’t host real buttons)
Screen reader treatment of content strings is inconsistent; assume decorative unless proven otherwise with testing.
Other pseudo-elements (map)
| Pseudo-element | Use |
|---|---|
::placeholder |
Input placeholder styling |
::selection |
Highlight colors |
::marker |
List markers |
::first-line / ::first-letter |
Typographic flourish |
Interview out-loud
“::before and ::after insert generated children. They need content, can participate in flex/grid, and should be decorative. Meaningful text stays in the DOM for accessibility. I use them for icons, underlines, and visual badges, with accessible names on the real controls.”
Footguns
- Missing
content— “why isn’t my ::after showing?” - Putting required text only in
content. position: absolutewithout positioned parent.- Animating huge pseudo-elements with filters on many nodes.
- Confusing pseudo-classes (
:hover) with pseudo-elements (::before).
Required indicator pattern
.label--required::after {
content: " *";
color: var(--danger);
}
.label--required::after {
/* Better: aria-hidden decorative star with text alternative in the label */
}
Prefer including “(required)” in the accessible name or aria-required on the control. Decorative stars alone fail some users. If you use ::after for the star, keep the word “required” available to AT via the label text or aria-describedby.
Empty states
.list:empty::before {
content: "No items yet.";
color: var(--text-muted);
}
Handy, but screen readers may ignore pure CSS content — consider a real DOM empty state for critical UX.
Related
- Pseudo-classes hover focus focus-visible
- CSS animations keyframes
- BEM naming methodology
- Semantic HTML
Further reading
Related guides
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.
- box-sizing border-boxWhy border-box makes width include padding and border, the global reset pattern, and the layout bugs content-box still causes.