data-spoiler
Hide content behind blur, solid, or noise overlays. Click or press Enter to reveal. Works on any HTML element with full keyboard and screen reader support.
Overview
The data-spoiler attribute conceals content until the user chooses to reveal it. Perfect for spoilers, quiz answers, and progressive disclosure. No wrapper element needed — add the attribute to any element.
<div data-spoiler> <p>Darth Vader is Luke's father.</p></div>
How It Works
Add data-spoiler to any element. The init script wraps the content in a content wrapper with inert, overlays a trigger button, and applies the chosen visual effect.
- Click or Enter on the trigger to reveal
- Escape to re-hide (unless
data-spoiler-persistis set) - A "Hide" button appears after reveal for mouse users
- Screen readers hear "Spoiler revealed" via a live region
Without JavaScript, content is fully visible — progressive enhancement ensures nothing is ever lost.
Attributes
| Attribute | Values | Description |
|---|---|---|
data-spoiler |
"", "blur", "solid", "noise" |
Marks element as a spoiler. Value sets the effect (default: blur). |
data-spoiler-label |
string | Custom text for the reveal button. Default: "Reveal spoiler". |
data-spoiler-persist |
boolean | One-way reveal. No re-hide button, Escape does nothing. |
data-spoiler-group |
string | Mutual exclusion group. Revealing one hides others in the same group. |
data-spoiler-init |
boolean | Set automatically to prevent double-binding. Do not set manually. |
data-spoiler-visible |
boolean | Present when content is revealed. Set/removed automatically. |
Effects
Blur (default)
Content is visible but unreadable behind a Gaussian blur.
This text is blurred until you reveal it.
<div data-spoiler> <p>Darth Vader is Luke's father.</p></div>
Solid
Content is completely hidden. The trigger shows a solid bar matching the text color.
Hidden behind a solid overlay.
<div data-spoiler="solid"> <p>Secret content hidden behind a solid overlay.</p></div>
Noise
Content is faintly visible through an SVG turbulence noise pattern.
Obscured by a noise pattern.
<div data-spoiler="noise"> <p>Hidden answer behind a noise pattern.</p></div>
Inline Spoilers
Use on inline elements like <span> for spoilers within a paragraph. The script uses <span role="button"> instead of <button> to maintain valid HTML inside <p>.
In the movie, the butler did it which shocked everyone.
<p>The movie ends when <span data-spoiler>the hero dies</span>.</p>
Custom Label
Set data-spoiler-label to customize the reveal button text.
The answer is 42.
<div data-spoiler data-spoiler-label="Show answer"> <p>The answer is 42.</p></div>
Grouped Spoilers
Spoilers with the same data-spoiler-group value are mutually exclusive. Revealing one automatically hides others in the group.
Paris
London
Tokyo
<div data-spoiler data-spoiler-group="answers" data-spoiler-label="Answer 1"> <p>Paris</p></div><div data-spoiler data-spoiler-group="answers" data-spoiler-label="Answer 2"> <p>London</p></div><div data-spoiler data-spoiler-group="answers" data-spoiler-label="Answer 3"> <p>Tokyo</p></div>
Persistent Reveal
Add data-spoiler-persist for a one-way reveal. The re-hide button is omitted and Escape does nothing.
Once revealed, this content stays visible.
<div data-spoiler data-spoiler-persist> <p>Once revealed, this cannot be re-hidden.</p></div>
Events
The host element dispatches a spoiler:toggle event on reveal and conceal.
| Event | Detail | Description |
|---|---|---|
spoiler:toggle |
{ visible: boolean } |
Fired when the spoiler is revealed or concealed. |
const spoiler = document.querySelector('[data-spoiler]'); spoiler.addEventListener('spoiler:toggle', (e) => { console.log('Visible:', e.detail.visible);});
Styling
All CSS rules are gated on [data-spoiler-init]. Without JavaScript, no concealment is applied — content is always visible.
/* All CSS is gated on [data-spoiler-init] — no JS means no concealment */[data-spoiler][data-spoiler-init] { position: relative;} /* Customise the reveal label */[data-spoiler-trigger] > [data-spoiler-label] { background: var(--color-surface); padding: var(--size-2xs) var(--size-xs); border-radius: var(--radius-s);}
Dynamic Elements
Elements added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.
<section> <h2>Accessibility</h2> <ul> <li><code>inert</code> on hidden content — screen readers cannot read it, keyboard cannot reach it</li> <li><code>aria-expanded</code> on the trigger button tracks revealed state</li> <li><code>role="group"</code> with <code>aria-label="Spoiler"</code> on the host element</li> <li>Live region announces "Spoiler revealed" on reveal</li> <li>Keyboard: Tab to trigger, Enter/Space to reveal, Escape to re-hide</li> <li>Inline spoilers use <code><span role="button" tabindex="0"></code> instead of <code><button></code> for valid HTML inside <code><p></code></li> <li><code>prefers-reduced-motion</code>: transitions are instant</li> <li>Without JavaScript, content is fully visible (progressive enhancement)</li> </ul> </section>