Loading page…
Type that arrives under its own power. Words rise out of blur, a chromatic edge sweeps a headline, letters cascade into a new label, and numbers roll like an odometer. Every primitive reads useReducedMotion and lands on the plain text when motion is off.
TextReveal splits a string into words or characters and staggers each unit up through blur. Pass an array for hard line breaks; the stagger keeps counting across lines.
Every ship builtfrom the same charts.
Default. One unit per word — the house entrance for headlines.
PRIVATEER
Per-letter. Reserve it for short marks — long copy reads as noise.
Cold storage, warm hands.
delay defers the start, blur=0 drops the focus pull, yOffset sets the rise.
Charted, then sailed.
spring takes stiffness/damping/mass — feed it the unfold spring from the token package.
import { TextReveal } from "@coldharbor/webui";
import { springs } from "@coldharbor/tokens/motion";
<TextReveal
as="h1"
text={["Every ship built", "from the same charts."]}
split="word"
stagger={0.09}
spring={{ stiffness: springs.unfold.stiffness, damping: springs.unfold.damping }}
/>--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-mutedwhileInView holds the units at their initial state until the element crosses 40% of the viewport. once=false rearms the reveal on every entry.
The tide keeps its own schedule.
Fires on entry. Above the fold, leave it off — a headline that waits reads as a bug.
<TextReveal text="The tide keeps its own schedule." whileInView once />--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-mutedChromaticTextReveal holds a fixed prefix and cycles the final word behind a moving chromatic edge. Colors are viz tokens, never literals; foregroundColor is the settled text.
pauseDuration rests between words; loop=false stops on the last one.
<ChromaticTextReveal
prefix="Coldharbor holds the"
words={["keys", "charts", "line"]}
colors={["var(--ch-viz-1)", "var(--ch-viz-2)", "var(--ch-viz-3)"]}
foregroundColor="var(--ch-fg)"
startOnView
loop
/>--ch-viz-1--ch-viz-2--ch-viz-3--ch-viz-4--ch-fgTextCascade re-letters one label into another — the old glyphs drop away left to right as the new ones land. It is a swap, not an entrance: change text to run it.
Four tx phases on a 2.4s timer through a single TextCascade.
import { TextCascade } from "@coldharbor/webui";
<TextCascade text={phase} className="font-display text-xl" />--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-mutedTextShimmer sweeps a highlight across a clipped gradient. Pure CSS keyframes — no motion/react, safe in a Server Component, and cheap enough to leave running.
Waiting states — signing, broadcasting, syncing.
Scanning mempool
Faster sweep for tight inline labels.
<TextShimmer as="p" duration={2.5}>Awaiting broadcast…</TextShimmer>--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-mutedTwo readouts, two jobs. NumberTicker rolls each digit column like an odometer and only moves the columns that changed — right for balances, heights, counts that step. AnimatedNumber tweens one value to the next and renders every number in between — right for totals, percentages, gauges where the sweep itself is the information.
872,411 blocks
#000042
Digit roll. pad fixes the column count; locale adds separators and is Server-Component safe; blur smears each column as it rolls.
0
0
Tween. format is client-only — the whole component is, so it must be called from a client boundary.
// Steps — block height, confirmations, sat balances.
<NumberTicker value={height} locale startOnView />
// Sweeps — a fee total settling to its final figure.
<AnimatedNumber value={total} duration={1.2} format={(n) => n.toFixed(2)} />--ch-fg--ch-fg-muted--ch-motion-ease-outBoth readouts fed the same climbing block height. The ticker moves only the ones column each block; the tween re-runs end to end. startOnView=false arms them immediately instead of waiting for the viewport.
NumberTicker — digit roll
872,411
AnimatedNumber — tween
0
Height climbs one block every three seconds. The ticker rolls only the digits that changed; the tween walks every value between.
<NumberTicker value={height} locale startOnView={false} />
<AnimatedNumber value={height} startOnView={false} />--ch-fg--ch-fg-muted--ch-motion-ease-outUnder prefers-reduced-motion every primitive keeps the words and drops the travel. Nothing here hides content behind an animation that never runs.
No rise, no blur — units fade in over 250ms with the stagger cut to a third.
Sweep lands finished; the word cycle stops, so the first word stands as plain foreground text.
Per-letter cascade falls back to a single-element swap with no entrance transition. Screen readers always get the whole label.
Digit columns jump to position with zero duration and the blur pass is skipped. The sr-only figure is the real value at all times.
Snaps straight to the target value — no intermediate frames.
CSS keyframes keep sweeping. Wrap it yourself where a resting label is required.
--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-mutedReveal, shimmer and ticker on the canvas layer.
Charts before cargo.
Awaiting broadcast…872,411
--ch-motion-duration-base--ch-motion-duration-slow--ch-motion-ease-out--ch-fg--ch-fg-muted