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.
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:
- 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
- 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.
CSS Skeleton Loading (Related Context)
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
contenton::before/::afterdoes 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-placeholderattributes alongsidearia-hiddento make placeholder content searchable and removable before production - CSS skeleton loaders use background color shapes, not text content — different pattern, different accessibility approach