Ad – 728×90
🔧 Advanced CSS

CSS Pseudo-elements – Generated Content & Styling Sub-parts

A CSS pseudo-element targets a specific sub-part of an element — like the first letter of a paragraph, the placeholder text of an input, or virtual elements inserted before or after the content. Unlike pseudo-classes, which select existing elements in a state, pseudo-elements either target a portion of an element's rendering or create virtual nodes that exist only in CSS. The double-colon syntax (::) distinguishes them from pseudo-classes (:).

⏱️ 18 min read 🎯 Intermediate 📅 Updated 2026 👁️ Lesson 3 of 5

::before and ::after

The most-used pseudo-elements. They insert a virtual child node before or after the element's content. Require a content property — even an empty string — to render.

CSS – ::before and ::after basics
/* Basic usage — content is required */
.icon-label::before {
  content: "★ ";
  color: gold;
}

.external-link::after {
  content: " ↗";
  font-size: 0.75em;
  color: #888;
}

/* Empty content — used for decorative shapes */
.divider::after {
  content: "";
  display: block;
  height: 2px;
  background: linear-gradient(to right, #1572B6, transparent);
  margin-top: 1rem;
}

/* Tooltip using ::after + :hover */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);   /* read from HTML attribute */
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: #333;
  color: #fff;
  padding: 4px 8px;
  border-radius: 4px;
  white-space: nowrap;
  font-size: 0.8rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
[data-tooltip]:hover::after { opacity: 1; }

Classic Clearfix with ::after

CSS – Clearfix
/* Modern approach — use display:flow-root or overflow:hidden */
/* But the clearfix is still useful to understand */
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

Decorative Patterns with ::before / ::after

CSS – Decorative pseudo-elements
/* Underline accent on headings */
h2 { position: relative; padding-bottom: 12px; }
h2::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  width: 48px;
  height: 3px;
  background: #1572B6;
  border-radius: 2px;
}

/* Quote marks */
blockquote::before {
  content: "\201C";   /* left double quotation mark */
  font-size: 4rem;
  line-height: 0;
  vertical-align: -1rem;
  color: #1572B6;
  margin-right: 4px;
}

/* Badge / required field marker */
label.required::after {
  content: " *";
  color: #e44d26;
  font-weight: bold;
}

/* Ribbon corner on a card */
.card.ribbon { overflow: hidden; position: relative; }
.card.ribbon::before {
  content: "NEW";
  position: absolute;
  top: 14px;
  right: -20px;
  background: #e44d26;
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 3px 28px;
  transform: rotate(45deg);
  letter-spacing: 0.05em;
}
Ad – 336×280

::first-line and ::first-letter

CSS – ::first-line and ::first-letter
/* ::first-line — styles the first rendered line of a block */
p::first-line {
  font-weight: 600;
  color: #1a1a1a;
  letter-spacing: 0.03em;
}
/* Only certain properties work on ::first-line:
   font properties, color, background, word-spacing,
   letter-spacing, line-height, text-decoration, text-transform */

/* ::first-letter — styles the first letter/character of a block */
article > p:first-of-type::first-letter {
  float: left;
  font-size: 3.5rem;
  line-height: 0.8;
  margin: 4px 8px 0 0;
  color: #1572B6;
  font-weight: 700;
}
/* Drop cap effect — classic magazine-style opening letter */

::placeholder and ::selection

CSS – ::placeholder and ::selection
/* ::placeholder — styles the placeholder text of an input */
input::placeholder,
textarea::placeholder {
  color: #aaa;
  font-style: italic;
  font-size: 0.9em;
}

/* ::selection — styles the highlighted (selected) text */
::selection {
  background: #1572B6;
  color: #ffffff;
}

/* Per-element selection color */
.code-block::selection,
.code-block *::selection {
  background: #264f78;
  color: #ffffff;
}

::marker

Styles the bullet or number of a list item — no need to remove the default bullet and add your own with ::before anymore.

CSS – ::marker
/* Style list bullets */
li::marker {
  color: #1572B6;
  font-size: 1.2em;
}

/* Custom content for ordered lists */
ol li::marker {
  color: #e44d26;
  font-weight: 700;
}

/* Emoji bullets */
ul.checklist li::marker {
  content: "✓ ";
  color: #2e7d32;
}

::backdrop

Styles the full-viewport backdrop that appears behind modal dialogs, fullscreen elements, and popover elements.

CSS – ::backdrop
/* Style the backdrop of a  element */
dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
}

/* Fullscreen video backdrop */
video::backdrop {
  background: #000;
}

📋 Summary

  • ::before / ::after — virtual nodes inserted inside the element. Require content:. Use for decorations, tooltips, clearfix, badges.
  • content: attr(data-x) — reads HTML attribute value into generated content.
  • ::first-line / ::first-letter — style the first rendered line or first character of a block. Limited property support.
  • ::placeholder — style input placeholder text: color, font-style, font-size.
  • ::selection — change highlight color when user selects text.
  • ::marker — style list item bullets/numbers directly, including content: for emoji bullets.
  • ::backdrop — style the overlay behind <dialog> or fullscreen elements.
  • Double colon (::) is required in modern CSS. Single colon (:) still works for legacy reasons but avoid it for new code.

Frequently Asked Questions

Why must ::before and ::after have a content property? +

The content property is what makes the pseudo-element exist in the render tree. Without it, the browser does not generate the pseudo-element box at all — it's as if the rule doesn't exist. For decorative uses (shapes, dividers), set content: "" — an empty string still generates the box. For text, set content: "your text". You can also use content: attr(data-attribute) to pull text from an HTML attribute.

Can I add event listeners to ::before / ::after elements? +

No — pseudo-elements are not part of the DOM. They exist only in the CSS render tree, so JavaScript cannot select them with querySelector or add event listeners. Clicks on a pseudo-element area are received by the real element, not the pseudo-element itself. If you need a clickable element, add a real DOM node.

What is the difference between a pseudo-class and a pseudo-element? +

A pseudo-class (single colon, e.g. :hover) selects a real element in a particular state. A pseudo-element (double colon, e.g. ::before) either targets a sub-part of an element's rendering (like ::first-letter) or creates a virtual element that doesn't exist in the HTML (like ::before and ::after). There is also a practical distinction: you can have multiple pseudo-classes stacked on one element, but only one pseudo-element per selector chain.