← The Library
Practical Guides

CSS Lorem Ipsum: Placeholder Text Without JavaScript

Using ::before { content: '' } for placeholder text, why it harms screen readers, when it is acceptable, and the better alternative with aria-hidden.

5 min

CSS Lorem Ipsum: Placeholder Text Without JavaScript

Generated Content as Placeholder Text

CSS can generate text content without HTML using the content property on ::before and ::after pseudo-elements. It is technically possible to use this approach to insert lorem ipsum placeholder text into empty or skeletal HTML structures — no JavaScript, no pre-rendered content:

.placeholder-body::before {
  content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
  display: block;
}

This works, in a narrow sense. The text appears. But it comes with consequences that make it almost always the wrong approach for placeholder text.

The Screen Reader Problem

CSS-generated content via content is exposed to the accessibility tree — meaning screen readers will announce it. A screen reader encountering .placeholder-body::before will read "Lorem ipsum dolor sit amet consectetur adipiscing elit" to the user.

This is a problem even for placeholder text used during development, because:

  1. Developers running accessibility tests against a prototype may receive false positives — the structure "passes" because the content is there, even though real content will behave very differently
  2. If the CSS ever reaches a user-facing environment (staged preview, accidentally deployed prototype), screen reader users encounter content that cannot be meaningfully parsed

The common misunderstanding is that CSS-generated content is "invisible" to accessibility tools. It is not. CSS content is in the accessibility tree unless explicitly hidden.

When CSS-Generated Placeholder Text Is Acceptable

There is one narrow context where CSS-generated text is acceptable: purely decorative backgrounds where the text is not intended to communicate anything — a watermark pattern, a background texture, a visual effect:

/* Decorative only — must have aria-hidden on the element */
.watermark::before {
  content: "DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT";
  position: absolute;
  opacity: 0.05;
  font-size: 4rem;
  letter-spacing: 0.5em;
  transform: rotate(-35deg);
  pointer-events: none;
  aria-hidden: true; /* This does NOT work on pseudo-elements — see below */
}

However, there is a complication: aria-hidden cannot be applied directly to pseudo-elements in CSS. To hide generated content from the accessibility tree, you must apply aria-hidden="true" to the parent HTML element:

<!-- Apply aria-hidden to the actual element, not the pseudo-element -->
<div class="watermark" aria-hidden="true"></div>
.watermark::before {
  content: "DRAFT DRAFT DRAFT DRAFT DRAFT";
  /* decorative styles */
}

With aria-hidden="true" on the parent, both the element and its generated content are hidden from the accessibility tree entirely. This is the correct approach for decorative CSS-generated text.

The Better Alternative: HTML with aria-hidden

For any structural placeholder text — body copy, headings, captions in a prototype or skeleton layout — the correct approach is real HTML content with aria-hidden="true" applied:

<!-- Accessible approach: HTML placeholder text hidden from screen readers -->
<section>
  <h2 aria-hidden="true">Lorem ipsum dolor sit amet</h2>
  <p aria-hidden="true">Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
  Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</section>

This approach:

  • Renders visually identically to the CSS approach
  • Does not expose meaningless content to screen reader users
  • Is explicitly removed from the accessibility tree
  • Makes it easy to search the codebase for aria-hidden="true" to find and replace placeholder content before production deployment

A development convention worth adopting: add a data-placeholder attribute alongside aria-hidden to make placeholder content easily searchable:

<p aria-hidden="true" data-placeholder="body-copy">
  Lorem ipsum dolor sit amet...
</p>

Then, before any production deployment, run a search for data-placeholder to locate all remaining placeholder content.

A related and legitimate use of CSS-generated content is skeleton loading screens — the gray placeholder shapes that appear while content loads. These use background-color and border-radius to simulate content layout, not content text. This is a different pattern and does not carry the same screen reader risks, since there is no text content involved:

.skeleton-text {
  background-color: #e5e7eb;
  border-radius: 4px;
  height: 1em;
  animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: 0.5; }
}

Skeleton loaders should still be properly attributed in the accessibility tree — typically with aria-busy="true" on the loading container and aria-label="Loading content".

Key Takeaways

  • CSS content on ::before/::after does insert text, but that text is exposed to the accessibility tree and announced by screen readers
  • CSS-generated placeholder text is only acceptable for purely decorative use (watermarks, background patterns) with aria-hidden="true" on the parent element
  • For structural placeholder text, use real HTML with aria-hidden="true" — this renders identically while correctly hiding content from assistive technology
  • Add data-placeholder attributes alongside aria-hidden to make placeholder content searchable and removable before production
  • CSS skeleton loaders use background color shapes, not text content — different pattern, different accessibility approach

Further Reading