Pseudo-elements

What You'll Learn

  • What pseudo-elements are and how they differ from pseudo-classes
  • How to create content with ::before and ::after
  • Styling the first letter and first line of text
  • Customizing text selection appearance
  • Creating CSS counters for automatic numbering
  • Advanced techniques like generated content and the attr() function

Introduction to Pseudo-elements

Pseudo-elements allow you to style specific parts of an element or insert content without adding extra HTML markup. While pseudo-classes (single colon :) select elements based on state, pseudo-elements (double colon ::) create virtual elements that style specific portions of content or generate new content entirely.

Pseudo-elements are incredibly powerful for adding decorative elements, creating complex layouts, and implementing patterns like clearfix—all without cluttering your HTML. They're virtual elements that don't appear in the DOM but are rendered by the browser as if they were real.

::before and ::after - Generated Content

The ::before and ::after pseudo-elements are the most commonly used pseudo-elements. They insert content before or after an element's actual content, perfect for adding decorative elements, icons, or additional text without modifying HTML.

Syntax

.quote::before { content: '"'; font-size: 4rem; color: #0066cc; opacity: 0.3; } .quote::after { content: '"'; font-size: 4rem; color: #0066cc; opacity: 0.3; } /* External link indicator */ .external-link::after { content: ' ↗'; font-size: 0.8em; color: #6c757d; }

HTML Example

<blockquote class="quote"> This text gets decorative quotation marks </blockquote> <a href="https://example.com" class="external-link"> External Link </a>

Rendered Result:

::first-letter - Drop Caps

The ::first-letter pseudo-element targets the first letter of a block-level element, allowing you to create classic typographic effects like drop caps without any extra markup.

Syntax

.drop-cap::first-letter { font-size: 4rem; font-weight: bold; float: left; line-height: 0.9; margin: 0.1rem 0.5rem 0 0; color: #0066cc; font-family: Georgia, serif; }

HTML Example

<p class="drop-cap"> This paragraph has a drop cap on the first letter. The effect is created entirely with CSS using the ::first-letter pseudo-element. </p>

Rendered Result:

Allowed Properties

Only certain CSS properties work with ::first-letter:

  • Font properties (font-family, font-size, font-weight, etc.)
  • Color properties (color, background-color)
  • Margin, padding, border
  • Text decoration
  • Vertical alignment (only if float is none)
  • Float and clear

::first-line - First Line Styling

The ::first-line pseudo-element styles the first line of a block-level element. It dynamically updates as the viewport changes, making it perfect for responsive designs.

Syntax

.article::first-line { font-weight: 600; color: #0066cc; font-size: 1.15rem; letter-spacing: 0.5px; }

HTML Example

<p class="article"> The first line is styled differently. This styling automatically updates when the window is resized and text reflows to different line breaks. </p>

Rendered Result:

Allowed Properties

::first-line supports a limited set of properties:

  • Font properties
  • Color and background properties
  • word-spacing, letter-spacing, text-decoration
  • text-transform, line-height
  • clear, vertical-align (if float is none)

::selection - Highlight Color

The ::selection pseudo-element customizes the appearance of text when users select it with their mouse or keyboard. This is a simple way to add brand personality and improve the user experience.

Syntax

/* Global selection style */ ::selection { background-color: #ffd700; color: #000; } /* Element-specific selection */ .highlight-text::selection { background-color: #0066cc; color: #fff; }

HTML Example

<p class="highlight-text"> Select this text to see custom highlighting! </p>

Rendered Result:

Allowed Properties

::selection supports very limited properties:

  • color
  • background-color
  • text-decoration and its sub-properties
  • text-shadow
  • -webkit-text-stroke-color, -webkit-text-fill-color, -webkit-text-stroke-width

Generated Content and the attr() Function

Pseudo-elements can generate content dynamically using the content property combined with functions like attr(), which pulls values from HTML attributes.

Common Patterns

/* Custom list bullets */ .custom-list li::before { content: '✓'; color: #28a745; font-weight: bold; margin-right: 0.5rem; } /* Display link URLs (useful for print stylesheets) */ a::after { content: ' (' attr(href) ')'; font-size: 0.85rem; color: #6c757d; } /* Required field indicator */ .required-label::after { content: ' *'; color: #dc3545; font-weight: bold; } /* Empty content for layout/clearing */ .clearfix::after { content: ''; display: table; clear: both; }

HTML Example

<ul class="custom-list"> <li>Custom bullet item</li> <li>Another item</li> </ul> <a href="https://example.com">Link</a> <label class="required-label">Email</label>

Rendered Result:

content Property Values

  • content: "text" - String literal
  • content: attr(attribute-name) - Attribute value
  • content: url(image.png) - Image
  • content: counter(name) - Counter value
  • content: "" - Empty (but element still renders)
  • content: none - Prevents rendering

CSS Counters

CSS counters allow you to create automatic numbering without JavaScript. They're perfect for section numbering, ordered lists with custom formatting, and complex nested numbering schemes.

Counter Properties

  • counter-reset - Initialize or reset a counter
  • counter-increment - Increment a counter
  • counter() - Display counter value
  • counters() - Display nested counter values

Syntax

/* Simple counter */ .numbered-list { counter-reset: section; list-style: none; } .numbered-list li::before { counter-increment: section; content: 'Section ' counter(section) ': '; font-weight: bold; color: #0066cc; } /* Nested counters */ .nested { counter-reset: chapter; } .nested > li::before { counter-increment: chapter; content: counter(chapter) '. '; } .nested ul { counter-reset: subsection; } .nested ul li::before { counter-increment: subsection; content: counter(chapter) '.' counter(subsection) ' '; }

HTML Example

<ol class="numbered-list"> <li>Introduction</li> <li>Main Content</li> <li>Conclusion</li> </ol>

Rendered Result:

Other Pseudo-elements

Beyond the commonly used pseudo-elements, there are several specialized ones for specific use cases:

::placeholder

Styles placeholder text in form inputs:

input::placeholder { color: #999; font-style: italic; opacity: 0.7; }

::marker

Styles list item markers (bullets/numbers):

li::marker { color: #0066cc; font-weight: bold; font-size: 1.2em; }

::backdrop

Styles the backdrop behind fullscreen or dialog elements:

dialog::backdrop { background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(4px); }

::file-selector-button

Styles the button in file input fields:

input[type="file"]::file-selector-button { background: #0066cc; color: white; border: none; padding: 0.5rem 1rem; cursor: pointer; }

Practical Use Cases

Pseudo-elements are incredibly versatile. Here are some real-world applications:

Clearfix Pattern

.clearfix::after { content: ''; display: table; clear: both; }

Clears floated children without extra markup.

Breadcrumb Separators

.breadcrumb li:not(:last-child)::after { content: ' / '; color: #999; margin: 0 0.5rem; }

Tooltips

[data-tooltip]::after { content: attr(data-tooltip); position: absolute; background: #333; color: white; padding: 0.5rem; border-radius: 4px; bottom: 100%; opacity: 0; pointer-events: none; transition: opacity 0.3s; } [data-tooltip]:hover::after { opacity: 1; }

Overlay Effects

.image-container::after { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.7)); }

Key Takeaways