Gather
A line whose letters, words or rows start as dust, as the mirrored forme or wound in a coil, and settle into place as it scrolls into view, or step by step with the scroll, then give way to the real text.
Report an issue with GatherExample
Everything settles in the end.
Set backwards, printed forwards.
A line wound up, let out.
Word by word, only as fast as you read.
Nothing is lost, only set down.
Point at it before it has arrived and it comes to you.
The type reads backwards until it is printed.
Each row unwinds from where it starts.
Fewer words, and every one in its place.
Each line is cast whole, a slug of type, and turned to be read.
Stop scrolling and the letters stop with you.
Scroll back and the words let go, one by one, the way they came.
The quiet between notes
A heading in an article settles as you reach it; the paragraph under it is already there to be read.
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/gather.jsonUsage
This example also uses Typography.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/ typography.jsonimport { Gather } from "@/components/ui/gather"import { Prose } from "@/components/ui/typography"export default function Example() {return (<div className="grid gap-12"><Gather as="h2" className="ot-f">Everything settles in the end.</Gather><Gather as="h3" variant="forme" className="ot-mf">Set backwards, printed forwards.</Gather><Gather as="h3" variant="coil" className="ot-mf">A line wound up, let out.</Gather><Gather as="h3" by="word" scrub className="ot-mf">Word by word, only as fast as you read.</Gather></div>)}Contract
- Underneath: native, plus a hook that measures every letter with pretext (
@chenglou/pretext, loaded when it's needed) and an IntersectionObserver. - Anatomy:
<h2 | h3 | p class="ot-gather" data-variant="forme | coil" data-by="word | line">holding the real text (.ot-gather-text) and, while it runs, an aria-hidden.ot-gather-glyphswith one span per piece (a letter, a word or a row), each carrying where it starts (--dx,--dy, and for the variants--sxor--r) and its place in the order (--n, and--sfrom 0 to 1).data-phaseiswait,dust,gatherorscrub, and is absent when it's plain. - A line whose letters start as dust: scattered by a fixed hash of their index, at most 1.2em off their place, at pp opacity. They settle into place when the line scrolls into view (once) or, if it hasn't yet, when it's pointed at. Each letter travels for
--ot-andante, one--ot-arpeggioafter the last. - Each letter's place is its x in the real line, kerning included, and its row, so the settled state is the real layout. When the last letter lands the real text is given back, so selecting and copying work; the text is in the DOM throughout for readers.
- forme: letterpress type stands in the chase mirrored and is read the right way round only once it's printed. Each row starts as its own mirror image, every letter flipped and at its mirrored place in the row, in pencil at full strength so the backwards line can be read; settling, each letter crosses to its place, turning over as it goes (it narrows to nothing halfway, like a sort turned in the fingers), and inks as it lands.
- coil: the text wound up like a ribbon. Every letter is set along one Archimedean spiral beside the first row's start (a little more than a letter's height between turns, the first letter on the outermost turn, each turned to the curve), at half strength; settling, the letters leave it in reading order, so it unwinds from its free end and lays itself along the line. Right to left the coil sits at the right and winds the other way.
- Pass a dynamic class (
ot-f,ot-mf…) for the size; the line wraps and aligns as the text would (start and end follow the direction). - The variants keep the one motion: the same travel, timing and hand-back as the dust; only where the letters start differs.
- by (
by,data-by): what travels as one piece, after Split Text. letter (the default) is each letter. word sets each word down whole, as "Less / is / more." stands a word to a row and "Healthy habits" sets its notes a word to a line; each leaves4 × --ot-arpeggioafter the last. line casts each row whole, as a Linotype casts a slug,8 × --ot-arpeggioapart. A piece is measured and placed as a letter is (its x in the real line, kerning included), so every variant works by any piece: a forme word or row is mirrored whole at its mirrored place, a coil word or row lies along the spiral turned to the curve. - scrub (
scrub,data-phase="scrub"): the settle follows the scroll instead of playing once, after scroll-reveal.--pruns from 0, as the line's top comes in at the foot of the view, to 1, once it's two fifths down from the top (or as far as the page can scroll; a line already in view when the page opens starts as dust and sets over the scroll left before that point). Each piece leaves as--ppasses its place in the order (--s × 0.6) and lands 0.4 of the scroll later, travelling linearly with no transition, so it stops when the scroll stops and scatters back when you scroll back. At 1 the real text is given back and the pieces wait out of sight; below 1 they take over again.--pis read from the real layout in a frame asked for by the scroll event, which runs after a smooth scroller's own frame (Lenis moves the page itself), so the letters and the page move in the same frame. Pointing does nothing in a scrub: the scroll is the only hand. By word it is word-stream: words arriving one by one as you read down. - In prose (article headings): put
Gatherin place of a heading insideProse(<Prose><Gather as="h2">…</Gather><p>…</p></Prose>). The heading takes the prose's size, weight and spacing, and settles as you reach it; the paragraphs under it are already there. The gather always wraps greedily as pretext does (text-wrap: wrap, over the prose's balance), so its letters land where the heading's own lines fall. - Under reduced motion, without script, or before pretext arrives, it's just the line. It measures again only when its width or face changes, so a scroll never cuts a settle short. Pending imports, font measurements and scroll frames stop when it is released; a failed font load restores the plain line.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
children | string | The line, as plain text: pretext measures where each letter belongs. | |
variant | "dust" | "forme" | "coil" | "dust" | Where the letters start. dust: scattered round their places at pp. forme: the line mirrored in pencil, as type stands in the chase before it is printed; each letter turns over as it crosses to its place and inks. coil: the whole text wound into one spiral beside the first row's start, first letter outermost; it unwinds from its free end. |
by | "letter" | "word" | "line" | "letter" | What travels as one piece. letter: each letter. word: each word whole, a few letters' time after the last. line: each row whole, as a Linotype casts a slug, twice that apart. Every variant works by any piece: a forme word or row is mirrored whole, a coil word or row lies along the spiral. |
scrub | boolean | false | Tie the settle to the scroll instead of playing it once. The line is dust as its top comes in at the foot of the view and set once it is two fifths down from the top (or as far as the page scrolls); each piece travels its share of that, in reading order, stops when the scroll stops, and scatters again when you scroll back. By word, this is words arriving one by one as you read down. Read from the real layout, so it keeps step with smooth scrolling. |
as | "h2" | "h3" | "p" | "p" | The element. Pass a dynamic class such as ot-f or ot-mf for the size. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Gather | Dust | The letters settle from scatter to the real layout |
| Forme gather | The letterpress forme, type set mirrored in the chase | The line starts as its own mirror image and prints the right way round |
| Coil gather | A ribbon wound on itself; WOVE's figures along an arc | The text starts wound in a spiral and unwinds onto the line |
| Gather by word, by line | "Less is more." a word to a row; the Linotype slug | Whole words, or whole rows cast at once, settle instead of letters |
| Scrub gather | The reader's own pace; a score read at sight | The settle is the scroll: it stops with you and undoes itself backwards |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Gather | Settle | The letters fly in from dust and land, one after another |
| Forme gather | Impression | Each letter turns over as it crosses from its mirror to its place, and inks |
| Coil gather | Unwind | The letters leave the spiral in reading order and lay themselves along the line |
| Scrub gather | Rubato (the reader's tempo) | The pieces travel only as far as the scroll has gone, forwards or back |