Editorial Reveal
A thin reveal primitive for editorial prose, callouts, and explainer beats. The wrapper owns no state of its own — the parent picks the current trigger and renders the matching children. When trigger flips, the previous body exits and the new body enters under the chosen variant (fade, lift, or blur).
Reach for it when a single slot of prose needs to morph in place between named beats — a hint that updates, an aside that swaps tone, an insight that lands after a struggle.
Now look again. Every comparison after the first one repeats work we already did. The structure has memory we are throwing away.
Customize
Beat
insight
Motion
lift
Installation
npx shadcn@latest add https://craftbits.dev/r/editorial-reveal.jsonUsage
import { EditorialReveal } from "@craft-bits/core";
<EditorialReveal trigger={beat} variant="lift">
{bodyForBeat(beat)}
</EditorialReveal>Use fade for the quietest swap — opacity only, no displacement:
<EditorialReveal trigger={beat} variant="fade">
{bodyForBeat(beat)}
</EditorialReveal>Use blur for a set-piece reveal where the content should feel like it is coming into focus:
<EditorialReveal trigger={beat} variant="blur">
{bodyForBeat(beat)}
</EditorialReveal>Understanding the component
- Keyed reveal. The body is wrapped in
<AnimatePresence mode="popLayout" initial={false}>and keyed bytrigger. Whentriggerchanges, the previous body exits and the new body enters under the variant's motion shape. initial={false}. The first render does not animate; only subsequenttriggerchanges do. Skips the first-render fade so the page does not flicker on load.mode="popLayout". Old and new bodies coexist mid-transition without pushing siblings around, so a tall body cross-fading with a short one does not cause the page to twitch.- Variant motion.
fadeis opacity only onSPRINGS.damped.liftadds a 6px upward translate onSPRINGS.smooth.bluradds a 6px filter blur onSPRINGS.damped. Every variant istransform/opacity/filteronly — neverwidth/height/top/left. - Reduced motion. When
prefers-reduced-motion: reduceis set, every variant collapses to an instant opacity cross-fade. - Aria-live polite. The body region carries
aria-live="polite"so screen readers re-announce the new content whentriggeradvances, without interrupting the user.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | string | number | required | Stable identifier for the current revealed body. Drives the AnimatePresence key. |
variant | 'fade' | 'lift' | 'blur' | 'lift' | Animation shape for the reveal. |
children | ReactNode | required | Body to reveal for the current trigger. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The body wrapper is
role="group"witharia-live="polite"so screen readers announce the new content on swap without interrupting. - Animation is
transform/opacity/filteronly — neverwidth/height/top/left. - Exiting bodies receive
pointer-events: noneso a mid-transition pointer cannot interact with content that is on its way out. prefers-reduced-motion: reduceshort-circuits every variant to an instant opacity cross-fade — no displacement, no blur, no spring.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/chrome/EditorialReveal.tsx). The source rendered a New Yorker / nan.fyi-styled em-dash lead-in with semantic-color variants baked into a single static paragraph. craft-bits' version drops the typographic lead-in (parent owns its own copy) and exposes a generic three-prop reveal API (trigger,variant,children) so the same primitive composes with any editorial flow.