content in CSS: the property that writes text the HTML never had

The content property inserts text, an attribute, or a counter into ::before and ::after, yet stays invisible to the DOM and screen readers.
4 min read
Believemy logo

The content property is one of the most surprising ones for beginners: it only applies to two specific pseudo-elements, yet it can insert text, a symbol, or even the value of an HTML attribute straight from a stylesheet, without touching the markup. It sits at the heart of countless interface tricks: custom bullets, decorative icons, generated tooltips, automatic numbering.

Understanding content also means understanding its limits: this generated content only exists visually, it never appears in the DOM, and it stays invisible to text selection or copying, which directly shapes where it makes sense to use it.


Definition

The content property defines what should be inserted inside a pseudo-element, most commonly ::before and ::after. Without it, these Pseudo-element simply do not exist in the render: content is the property that gives them content, even an empty one, and makes them visually exist.


Syntax and possible values

content accepts several kinds of values: a text string in quotes, the attr() function to read an HTML attribute, an image, or special keywords like none or normal.

CSS
.required::after {
  content: " *";
  color: red;
}

.external-link::after {
  content: " ↗";
}

.button::before {
  content: attr(data-label);
}

Even an empty string, content: "";, is enough to make the pseudo-element exist in the render: it is actually the most common value when ::before or ::after is used only to draw a shape with a border or a background color, without any text.


attr(): reading an HTML attribute's value

The attr() function reads the value of an HTML attribute already present on the element and inserts it as text. It is the most direct way to keep a piece of data in the HTML, where it makes semantic sense, while displaying it only through CSS.

CSS
a[title]::after {
  content: " (" attr(title) ")";
  font-size: 0.85em;
  color: gray;
}

This technique echoes the spirit of an Attribute selector, which targets an element based on the presence or value of an attribute: attr() goes one step further by reusing that same value as visible content, without duplicating the information in two different places.


CSS counters

content can also display the value of a CSS counter, an automatic numbering system entirely handled in CSS, with no script involved. You initialize and increment it with counter-reset and counter-increment, then display it with the counter() function inside content.

CSS
.toc {
  counter-reset: section;
}

.toc h3 {
  counter-increment: section;
}

.toc h3::before {
  content: "Section " counter(section) ": ";
}

This mechanism even works with nested lists, by combining several counters, which makes it possible to reproduce multi-level numbering, such as 1, 1.1, 1.2, purely in CSS, a common need for documentation or a table of contents.


Classic trap: invisible to selection and accessibility

Text generated by content does not exist in the DOM. It cannot be selected, copied, or searched with Ctrl+F, and most screen readers do not announce it by default. Using content to display genuinely useful information, like a price or an error message, effectively hides it from part of your users.

Good to knowReserve content for decoration: bullets, separators, icons, numbering. Any information that matters to the user should stay in the HTML, and be styled afterward with CSS if needed.


FAQ

QuestionCan content be used on any element?

No, the property only has an effect on the ::before and ::after pseudo-elements. Applied directly to a regular HTML element like a div or a paragraph, it is simply ignored by the browser.

QuestionIs text generated by content indexed by search engines?

Most modern search engines execute CSS and can technically see this text, but the handling stays inconsistent and unreliable: it is best never to rely on it for content that must be reliably indexed.

QuestionCan HTML be placed inside a content value?

No, content only inserts plain text or an image, never interpreted HTML markup: characters like < or > would simply be displayed as text, not rendered as elements.

Related terms

Discover our hTML and CSS glossary

Browse the terms and definitions most commonly used in HTML and CSS development.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.