CSS Pseudo-Elements: Style Parts of an Element That Don't Exist in HTML

Pseudo-elements like ::before, ::after, ::first-letter, and ::placeholder let you style pieces of an element you never wrote in HTML. Learn the syntax, the content property, and practical patterns you'll actually use.

Published August 6, 20269 min readBy ACY Partner Indonesia
CSS pseudo-elements — ::before and ::after with the content property
300 × 250Ad Space AvailablePlace your ad here

Up to now, every selector you’ve written has targeted a real element — a <p>, a <div>, something that actually exists in your HTML. Pseudo-elements break that rule in a useful way: they let you style a part of an element, or even create brand-new visual content, without writing a single extra tag in your markup.

Want to add a decorative icon before a heading? A custom quote mark around a blockquote? Style just the first letter of a paragraph like an old book? Add a “required” asterisk after a form label? That’s all pseudo-elements. They’re one of those features that, once they click, you reach for constantly. Let’s break them down.

What a pseudo-element actually is

A pseudo-element targets a specific portion of an element rather than the whole thing. The browser treats that portion as if it were its own little element you could style — even though it’s not in your HTML.

You write them with a double colon (::), which is what visually distinguishes them from pseudo-classes (which use a single colon, like :hover):

p::first-line {
  font-weight: bold;
}

That rule bolds only the first line of every paragraph — a chunk of text that has no tag of its own. You couldn’t select it any other way.

Double colon vs single colon

Pseudo-elements use two colons (::before), pseudo-classes use one (:hover). The double colon was introduced in CSS3 specifically to tell them apart. You’ll still see old code using a single colon for pseudo-elements (:before) — browsers accept it for backwards compatibility — but in any new code you write, use the double colon. It signals your intent clearly: “this targets a generated part of the element.”

If you’re still warming up on how selectors work in general, it’s worth circling back to CSS selectors first — pseudo-elements build directly on that foundation.

The big two: ::before and ::after

By far the most-used pseudo-elements are ::before and ::after. They let you insert generated content before or after an element’s actual content. Picture them as two invisible mini-elements living just inside your element, one at the very start and one at the very end.

Here’s the catch that trips everyone up at first: for ::before and ::after to show up at all, you must give them a content property. Without it, nothing appears.

.note::before {
  content: "💡 ";
}
<p class="note">Save your work often.</p>

That renders as 💡 Save your work often. — the lightbulb was never in the HTML; CSS conjured it. The content value can be plain text, an emoji, an empty string, or even nothing-but-a-shape (more on that shortly).

No content, no pseudo-element

This is the number-one beginner mistake. If you set content: ""; you get an empty but present box you can size and style. If you omit content entirely, the pseudo-element is not generated at all — your background, border, and width will do absolutely nothing because there’s no box to apply them to. Always include content, even if it’s just content: "";.

Generated content is “real” content

Whatever you put in content shows up on the page like normal text. That means a content: "→" actually places an arrow character that’s visible, copyable in some cases, and styleable with color, font-size, and the rest.

.external::after {
  content: " ↗";
  color: #00B8E6;
}
<a href="https://example.com" class="external">Visit ACY Partner Indonesia</a>

Now every link with the external class gets a small cyan arrow tacked on after the text, hinting that it opens elsewhere — a tidy UI touch with zero extra markup.

Making decorative shapes with empty content

Here’s where ::before and ::after get genuinely powerful. Set content: ""; (empty) and then give the pseudo-element a width, height, background, or border — and you’ve created a pure decoration: a divider, a dot, a custom underline, a little badge.

.title::after {
  content: "";
  display: block;
  width: 60px;
  height: 4px;
  background: #00B8E6;
  margin-top: 8px;
}
<h2 class="title">Our Services</h2>

That paints a short accent bar underneath the heading. The pseudo-element holds no text at all — its entire job is to be a styled box. This pattern shows up everywhere: section underlines, card ribbons, checkmarks, custom bullets, speech-bubble tails.

Pseudo-elements are inline by default

A fresh ::before or ::after behaves like an inline element, so width and height are ignored until you change that. Add display: block (or inline-block) to make it respect dimensions. This is why the example above sets display: block before giving it a width and height. If your decorative box “won’t size,” this is almost always why. It also helps to know how the display property changes an element’s box behavior.

A practical example: custom list bullets

Default list bullets are limited and hard to style. With ::before you can replace them with anything — an arrow, a checkmark, a colored dot of exactly the size you want:

.checklist {
  list-style: none;   /* remove the default bullets */
  padding-left: 0;
}

.checklist li::before {
  content: "✓ ";
  color: #16a34a;
  font-weight: bold;
}
<ul class="checklist">
  <li>Mobile friendly</li>
  <li>Fast loading</li>
  <li>SEO optimized</li>
</ul>

You strip the native bullets with list-style: none, then prepend your own green checkmark via ::before. Now you control the exact symbol, color, and spacing — something the built-in bullets never let you do.

Typographic pseudo-elements

A handful of pseudo-elements target text specifically. These don’t need a content property — the text they style already exists.

::first-letter and ::first-line

::first-letter styles just the opening letter of a block — perfect for the classic “drop cap” you see at the start of magazine articles. ::first-line styles the entire first line, however long it happens to be.

.article p::first-letter {
  font-size: 3em;
  font-weight: bold;
  float: left;
  line-height: 1;
  margin-right: 6px;
  color: #00B8E6;
}

The first letter of each paragraph balloons into a large, floated drop cap while the rest of the text wraps neatly around it. Because ::first-line reflows with the text, its boundary shifts as the window resizes — the browser recalculates what “the first line” is on the fly.

::selection

::selection styles the part of the page the user has highlighted with their cursor. It’s a small detail that makes a site feel polished and on-brand:

::selection {
  background: #00B8E6;
  color: #ffffff;
}

Now whenever someone drags to select text, the highlight uses your brand cyan instead of the browser default. Only a few properties work here (color, background, text-decoration, and a couple of others), but that’s plenty for a clean highlight.

::placeholder

::placeholder styles the faint hint text inside an empty <input> or <textarea>. By default it’s a flat gray; this lets you match it to your design:

input::placeholder {
  color: #94a3b8;
  font-style: italic;
}
<input type="email" placeholder="you@example.com">

It’s a tiny touch, but consistent placeholder styling is one of those things that quietly separates a careful interface from a sloppy one.

Positioning pseudo-elements precisely

For more advanced decorations — tooltips, badges, ribbons, corner flags — you’ll often want to place a pseudo-element at an exact spot. The standard recipe is to make the parent position: relative and the pseudo-element position: absolute:

.badge {
  position: relative;
}

.badge::after {
  content: "NEW";
  position: absolute;
  top: -10px;
  right: -10px;
  background: #ef4444;
  color: #fff;
  font-size: 12px;
  padding: 2px 6px;
  border-radius: 4px;
}
<button class="badge">Pricing</button>

That pins a little red “NEW” tag to the top-right corner of the button, floating just outside its edge. The position: relative on the parent gives the absolutely-positioned pseudo-element something to anchor to. If positioning feels shaky, a quick refresher on CSS positioning will make this pattern much clearer.

A few gotchas worth knowing

A handful of rules will save you debugging time:

  • They only work on elements that have content. You can’t add ::before/::after to “empty” replaced elements like <img>, <input>, <br>, or <hr>. These elements don’t have a content area for the pseudo-element to live in, so it simply won’t render.
  • Generated content isn’t selectable like normal text in many browsers, and screen readers may or may not announce it. Don’t put important information — text the user genuinely needs to read — inside content. Keep it decorative.
  • You can have one ::before and one ::after per element, not several of each. Two decorations is the limit per element; for more, you’ll nest extra elements.
  • content can pull in an attribute’s value using attr(), which is genuinely handy:
a::after {
  content: " (" attr(href) ")";
  color: #64748b;
}

That appends each link’s URL in parentheses right after the link text — useful for print stylesheets where the reader can’t click.

Don't hide essential content in a pseudo-element

Because generated content can be skipped by assistive technology and isn’t part of the real document, never use ::before/::after for content that must be read — labels, prices, error messages, button text. Use them for decoration and visual flourishes only. If the information matters, put it in your HTML where everyone, including screen readers and search engines, can reach it.

Wrapping up

Pseudo-elements let you style and even invent parts of an element that don’t exist as real tags — a clean way to add decoration without cluttering your HTML. Here’s the core to carry forward:

  • Pseudo-elements use a double colon (::before), unlike pseudo-classes’ single colon (:hover).
  • ::before and ::after insert generated content and always need a content property — even content: "" for a pure-decoration box.
  • Set display: block on ::before/::after so width and height take effect for shapes like underlines and dividers.
  • ::first-letter, ::first-line, ::selection, and ::placeholder style text-level pieces with no content needed.
  • Pair position: relative on the parent with position: absolute on the pseudo-element to place badges and ribbons precisely.
  • Keep generated content decorative — never hide essential, must-read information inside it.

Master ::before and ::after and you’ll spot dozens of places they simplify your markup. They pair especially well with the styling you’ve learned so far — and once you start combining them with transitions and transforms, you’ll have everything you need to build polished, animated UI details from CSS alone.

Tags:cssfrontendpseudo-elementsbefore-afterintermediate
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read