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-persist is 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.

// Dynamically added spoilers are auto-enhanced via MutationObserver const el = document.createElement('div'); el.dataset.spoiler = 'blur'; el.textContent = 'Hidden content'; document.body.appendChild(el); // el is ready to use — no manual init needed

Accessibility

  • inert on hidden content — screen readers cannot read it, keyboard cannot reach it
  • aria-expanded on the trigger button tracks revealed state
  • role="group" with aria-label="Spoiler" on the host element
  • Live region announces "Spoiler revealed" on reveal
  • Keyboard: Tab to trigger, Enter/Space to reveal, Escape to re-hide
  • Inline spoilers use <span role="button" tabindex="0"> instead of <button> for valid HTML inside <p>
  • prefers-reduced-motion: transitions are instant
  • Without JavaScript, content is fully visible (progressive enhancement)