Skip to content
WANCRAFT

C.134Typography & Text

Spoiler Text

Text hidden under shimmering particle noise that dissolves outward from where you click.

NewReactCanvasTailwindadvanced

Overview

Spoiler Text covers a phrase or a whole passage with drifting, twinkling particle noise drawn on a canvas. Clicking, tapping or pressing Enter/Space dissolves the noise outward from the press point (or the centre for keyboard users): particles inside the expanding wave are flung away as they fade, and in block mode the text is unveiled by a growing radial mask. Inline spoilers read every line box with getClientRects, so they wrap inside paragraphs and each line gets its own particle field. It is a real button with aria-expanded; until revealed, the content is aria-hidden and the button is named "Reveal spoiler", so screen readers cannot leak it. Optional re-hide, a controlled API, and the loop pauses offscreen, in hidden tabs and once revealed.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/spoiler-text.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/spoiler-text.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/spoiler-text.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { SpoilerText } from "@/components/ui/spoiler-text";

export function SpoilerTextExample() {
  return (
    <p className="max-w-md text-lg leading-relaxed">
      In the last chapter we learn that{" "}
      <SpoilerText>
        the lighthouse keeper has been dead since page one
      </SpoilerText>
      .
    </p>
  );
}

03Props

PropTypeDefaultDescription
childrenReactNode—The hidden content. Phrasing content in inline mode; any content in block mode.
mode"inline" | "block""inline"Inline spoilers wrap with the sentence (per-line noise); block covers a panel.
revealed / defaultRevealedboolean— / falseControlled or initial revealed state.
onRevealedChange(revealed: boolean) => void—Called when the spoiler is revealed or covered again.
rehidebooleantrueClicking a revealed spoiler covers it again. When false it becomes plain text after reveal.
densitynumber1Particle density multiplier (1 ≈ one particle per 12px²).
speednumber1Drift and twinkle speed multiplier; 0 freezes the grain.
revealDurationnumber650Length of the outward dissolve in ms (0 = instant).
accentstring"#FF7448"Focus ring and spark colour.
labelstring"Reveal spoiler"Accessible name of the button while hidden.
hintstring"Spoiler · click to reveal"Caption shown over a hidden block spoiler (empty string hides it).
...propsHTMLAttributes—Forwarded to the root <span> (inline) or <div> (block).

04Features

  • 01Canvas particle noise that drifts and twinkles in the text colour, with occasional accent sparks
  • 02Dissolves outward from the click/tap point; particles are flung away as the wave passes
  • 03Inline mode follows every wrapped line via getClientRects; block mode adds a growing radial mask
  • 04Optional re-hide (ignored while text is being selected), controlled or uncontrolled
  • 05Resolves oklch/any CSS colours through a 1×1 canvas probe and follows theme switches
  • 06Loop pauses offscreen (IntersectionObserver), in hidden tabs and once revealed; DPR capped at 2
motion · continuousmotion · tweeninput · clickinput · keyboard

05Accessibility

  • The spoiler is a focusable role="button" with aria-expanded; Enter and Space reveal it (and hide it again when re-hide is on).
  • While hidden the content is aria-hidden and unselectable, and the button is named "Reveal spoiler", so assistive tech never reads it early.
  • Once revealed the content is exposed normally; without re-hide the button role is dropped and the text becomes plain text, keeping focus in place.
  • The particle canvas and block caption are aria-hidden; focus-visible draws an accent outline.
  • prefers-reduced-motion freezes the grain and reveals instantly.

06Limitations

  • Hidden text is still in the DOM (and findable with the browser's find-in-page); this is a reading courtesy, not a security boundary.
  • Inline spoilers expect phrasing content (no block elements) so they can wrap inside a paragraph.