CSS: Pseudo-elements
Style specific parts of an element or insert decorative content without touching the HTML.
::before and ::after
::before and ::after insert a virtual element as the first or last child of the selected element. They are not in the HTML, the browser generates them purely from CSS. They must have a content property to appear.
::before and ::after
::before inserts a generated element before the element's content; ::after inserts one after. Both are inline by default and must have content: '' (even if empty) to render.
- content: ''required even if empty; without it the pseudo-element does not render
- display: blockset this when you need width, height, or block-level behaviour
- They are not part of the DOM, they cannot be selected by JavaScript
- They inherit styles from the parent element
- Use position: absolute on the pseudo-element with position: relative on the parent to place decorations precisely
::before and ::after
A label is prepended and a decorative mark appended using generated content.
.note {
padding: 12px 16px;
background: #f0f4f8;
border-radius: 4px;
font-size: 1rem;
}
.note::before {
content: "Note: ";
font-weight: bold;
color: steelblue;
}
.note::after {
content: " *";
color: crimson;
font-weight: bold;
}content Property
The content property is exclusive to ::before and ::after. It defines what is inserted: a string, nothing, a counter, or a URL to an image.
content
content can hold a text string, an empty string, a URL, or a counter value. It cannot hold HTML, only plain text and CSS values.
- content: ''empty string; renders the pseudo-element with no text (common for decorative shapes)
- content: 'text'inserts the literal string 'text'
- content: attr(href)inserts the value of the element's href attribute
- content: counter(section)inserts an auto-incrementing counter value
- content: url('icon.svg')inserts an image
- content cannot render HTML tags, use it for plain text or decorative shapes only
content: attr()
The href value is appended after the link text using attr().
.link {
color: steelblue;
text-decoration: none;
}
.link::after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #888;
}::first-letter and ::first-line
::first-letter targets the very first character of an element's text. ::first-line targets the first rendered line of text, which changes dynamically as the viewport resizes.
::first-letter and ::first-line
::first-letter only works on block-level elements. ::first-line targets the first visible line and updates automatically when the container resizes.
- ::first-letterclassic drop-cap effect; only works on block or inline-block elements
- ::first-linestyles only the first rendered line of text
- Both only accept a limited set of CSS properties (font, color, background, text-decoration, etc.)
- ::first-letter includes leading punctuation before the first letter
::first-letter
The first letter is enlarged and styled to create a drop-cap effect.
.article {
line-height: 1.7;
max-width: 480px;
}
.article::first-letter {
font-size: 3rem;
font-weight: bold;
color: steelblue;
float: left;
line-height: 1;
margin-right: 6px;
}
.article::first-line {
font-weight: 600;
color: #334155;
}::selection
::selection styles the portion of text that the user highlights by dragging their cursor. By default browsers show a blue highlight, you can override the background and text color.
::selection
::selection only accepts color, background-color, and text-shadow. Keep the contrast high so selected text remains readable.
- background-colorthe highlight colour behind selected text
- colorthe text colour of the selected characters
- Only these two properties (plus text-shadow) are supported inside ::selection
- Apply ::selection globally with *::selection for a site-wide selection colour
- Ensure sufficient contrast between the selection background and text colour
::selection
Selected text shows a custom steelblue background instead of the default blue.
.text {
font-size: 1.05rem;
line-height: 1.7;
padding: 12px;
background: #f8fafc;
border-radius: 4px;
}
.text::selection {
background-color: steelblue;
color: white;
}::placeholder
::placeholder styles the placeholder text inside an input or textarea. By default browsers render placeholder text in a light grey, you can change its color, font, or style.
::placeholder
::placeholder only affects the placeholder text, not the user's input. Keep placeholder text low-contrast compared to real input text so users can tell them apart.
- Placeholder text should never replace a proper label, it disappears when the user types
- colorchanges the placeholder text colour
- font-style: italica common convention to visually separate placeholder from real input
- opacitysome browsers use opacity to dim the placeholder; set it explicitly if needed
::placeholder
Placeholder text is styled with a custom colour and italic style.
.field {
display: block;
width: 100%;
padding: 10px 14px;
border: 2px solid #cbd5e1;
border-radius: 6px;
font-size: 1rem;
margin-bottom: 10px;
outline: none;
}
.field:focus { border-color: steelblue; }
.field::placeholder {
color: #94a3b8;
font-style: italic;
}Common Use Cases
Pseudo-elements are most useful for adding decorative details, dividers, badges, icons, and overlays without adding extra HTML elements. Below are two of the most common real-world patterns.
Common Pseudo-element Patterns
These patterns keep the HTML clean by handling purely decorative details in CSS, where they belong.
- Underline accentuse ::after with a coloured block to create a styled underline beneath a heading
- Badge / ribbonuse ::before with position: absolute to attach a label to a card
- Custom list bulletset list-style: none and use ::before with content to draw a custom bullet
- Overlayuse ::before with position: absolute, inset: 0 and a semi-transparent background on images
- Always set position: relative on the parent when the pseudo-element uses position: absolute
Heading underline accent
A coloured accent line is drawn under the heading using ::after, no extra HTML needed.
.section-title {
display: inline-block;
font-size: 1.5rem;
position: relative;
padding-bottom: 8px;
}
.section-title::after {
content: '';
display: block;
width: 50%;
height: 3px;
background: steelblue;
border-radius: 2px;
margin-top: 4px;
}Badge with ::before
A 'Popular' badge is positioned on the card corner using ::before.
.card {
position: relative;
border: 2px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
max-width: 240px;
overflow: hidden;
}
.card::before {
content: 'Popular';
position: absolute;
top: 12px;
right: -20px;
background: steelblue;
color: white;
font-size: 0.7rem;
font-weight: bold;
padding: 3px 28px;
transform: rotate(45deg);
}Generated Content Best Practices
Pseudo-elements are powerful but should be used thoughtfully. Because they are not real DOM elements, they have limitations that affect accessibility and maintainability.
Best Practices
Generated content via ::before and ::after is invisible to screen readers in most cases. Never use it for meaningful text, only for decorative details.
- Use pseudo-elements for decorative content only, icons, dividers, shapes, badges
- Never place important text inside content: '...', screen readers may ignore it
- Keep content: '' empty when using the pseudo-element purely for visual shapes
- Do not try to select pseudo-elements with JavaScript, they are not in the DOM
- Set display: block or display: inline-block when you need width and height on the pseudo-element
- box-sizing: border-boxapply to pseudo-elements just like regular elements if using padding and border
Custom bullets with ::before
Custom square bullets drawn with ::before, no list-style-image needed.
.custom-list {
list-style: none;
padding: 0;
margin: 0;
}
.custom-list li {
padding: 6px 0 6px 20px;
position: relative;
}
.custom-list li::before {
content: ''; /* decorative, no text needed */
display: block;
width: 8px;
height: 8px;
background: steelblue;
border-radius: 2px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
}Knowledge Check
1. What is the correct syntax for a pseudo-element?
2. Which property is required for ::before and ::after to appear?
3. What does ::first-letter target?
4. Which pseudo-element styles the text the user highlights with their cursor?
5. Which pseudo-element targets the placeholder text inside an input?
6. Which statement about ::before and ::after is correct?