Strike Reframe Text
A strike-and-reframe text React component: a sentence is struck through with a hand-drawn line, then a new sentence rises from below to replace it. Chainable for objection-to-answer and old-claim-to-new-claim reframes.
A text animation that strikes out the old claim and swaps in the new one. Each pair shows its sentence in full, then a slightly wobbly hand-drawn line sweeps across from left to right, the old sentence fades out of the way, and the new one springs up from below with a soft blur — optionally with a highlight brushed in behind it from left to right. Multiple pairs play in sequence, which suits landing-page pain-point flips, product update announcements or slide-style before/after arguments. Turn on keepStruck to keep the struck sentence above as a small strikethrough note.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add strike-reframe-text.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<StrikeReframeText/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/strike-reframe-text.en.jsonOr, once registries are configured in components.json, install it as @webberui/strike-reframe-text.
Usage
import { StrikeReframeText } from "@/components/ui/strike-reframe-text";
<StrikeReframeText
as="h2"
loop
pairs={[
{ from: "Reports eat a whole afternoon", to: "Reports generate in three minutes" },
{ from: "Reconciling means copy-pasting every time", to: "Statements reconcile themselves" },
]}
className="text-3xl font-semibold"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
pairs | StrikeReframePair[] | — | Pairs played in order; each strikes out from, then to rises to replace it |
as | "p" | "h2" | "span" | "p" | Root element tag; span renders inline-block so it can sit inside a paragraph |
autoPlay | boolean | true | Advance to the next pair automatically; when off, only the current pair plays |
trigger | "mount" | "inView" | "inView" | When playback starts: on mount, or once scrolled into view (fires once) |
loop | boolean | false | Return to the first pair after the last one finishes |
keepStruck | boolean | false | Keep the struck state: the old sentence shrinks and moves above as a strikethrough note |
highlight | boolean | true | Brush a highlight in from left to right behind the new sentence |
strikeDuration | number | 450 | Strike duration (ms); when omitted reads --wb-strike-reframe-duration, then falls back to 450 |
holdBefore | number | 900 | How long the old sentence is shown before the strike starts (ms) |
holdAfter | number | 1600 | How long the new sentence stays before moving to the next pair (ms) |
index | number | — | Controlled current pair index; when provided the parent switches pairs and the component only animates |
defaultIndex | number | 0 | Initial pair index in uncontrolled mode |
onIndexChange | (index: number) => void | — | Called when the component wants to advance; in controlled mode you decide whether to accept it |
className | string | — | Passed to the root element; set font size, weight and colour here |
StrikeReframePair
| Field | Type | Default | Description |
|---|---|---|---|
from | string | — | The old sentence shown first, then struck through |
to | string | — | The new sentence that rises from below to replace it |
How it works
- Per-pair timing: old sentence holds for
holdBefore→ strike overstrikeDuration(easing[0.65, 0, 0.35, 1]) → the new sentence springs up (stiffness 320, damping 28) from 18px below with a 4px blur → holds forholdAfter, then the next pair starts. - The strike is measured from the old sentence's actual layout (ResizeObserver, plus a re-measure once web fonts load). Wrapped sentences get one stroke per line, drawn in sequence. The wobble is derived from a hash of the sentence, so the same text always gets the same line and server and client output match.
- Override the look with CSS variables:
--wb-strike-reframe-line(line colour, default#f43f5e),--wb-strike-reframe-highlight(highlight colour, defaultrgb(250 204 21 / 0.35)) and--wb-strike-reframe-duration(strike duration, default450ms). - Old and new sentences share one grid cell, so the container takes the larger of the two and the layout does not jump when they swap; with
keepStruckthe root reserves space above for the shrunken strikethrough note. - In controlled mode the component only proposes the next pair through
onIndexChangeonce a pair finishes; if you do not updateindex, it stays on the current pair.
Accessibility
- The animated visual layer is
aria-hidden; screen readers get sr-only text reading "Before: old sentence; now: new sentence", so the meaning never depends on the animation. - A separate
aria-live="polite"region announces the new sentence only when the pair changes; nothing is announced on first load, to avoid repetition. - With
as="h2"the heading's accessible name is the full before/now sentence, not just the struck half. - When the user has enabled reduce motion in their OS, no line is drawn and nothing travels: the component cross-fades straight to the new sentence in 150ms and keeps the old one above as small strikethrough text.
Logo Reveal Set
Five common logo entrances (bloom, stroke-then-fill, shine sweep, lockup slide-out and wordmark morph) triggered on mount, on entering the viewport or manually.
Liquid Reveal
Refracts children through an SVG displacement filter like a liquid droplet, then bursts the surface to reveal them crisply on click or in-view.