How they work, real use cases, and why yours might not be showing.
They're virtual elements the browser inserts inside your element — one before the content, one after. They exist in the rendered page but not in your HTML. Toggle the properties below to see them live.
content is required on every ::before
and ::after. Even if you want a purely decorative shape with no text, you must write
content: "". Without it, the pseudo-element simply does not render — no error, just nothing.
These are the patterns developers actually use every day — all built with ::before
and ::after, zero extra HTML elements.
The lines are ::before and ::after with flex: 1 and height: 1px.
content: attr(data-tip) reads the HTML attribute
Badge uses ::after with position: absolute + content: attr(data-count)
"NEW" label is ::after on the wrapper — no extra HTML span needed.
Both lines are ::before + ::after with flex: 1.
Underline is ::after with width: 0 growing to 100% on hover.
You don't need extra HTML for decorative elements. That's what pseudo-elements are for.
Opening quote is ::before with content: "\201C".
Every case of ::before or ::after silently not showing comes down to one of these
five reasons. Click each one to see the broken code and the fix.
content property
This is the #1 reason. The content property is not optional — it is what tells the browser to render the pseudo-element at all. Even a purely decorative shape needs content: "".
img, input)
::before and ::after insert content inside an element. Void elements like <img>, <input>, <br>, and <hr> cannot have children — so pseudo-elements on them are ignored.
display set
Pseudo-elements default to display: inline. Inline elements ignore width, height, and vertical padding. If you're making a decorative block shape, you must set display: block or display: inline-block.
position: relative on the parent
When you use position: absolute on a pseudo-element to overlay it on the parent, the parent must have position: relative. Without it, the pseudo-element positions against the nearest positioned ancestor — often escaping the parent entirely.
overflow: hidden on parent
If position: absolute pseudo-elements extend outside the parent's bounds and the parent has overflow: hidden, the pseudo-element gets clipped. This is especially common with decorative glows, halos, and border effects.
content property present? (2) Not on img/input?
(3) display: block set if using width/height? (4) Parent has position: relative?
(5) No overflow: hidden clipping it?