Contour
A paragraph set to a contour, the way a score draws a swell: each line laid to its own width, so the words taper, open or swell and close.
Report an issue with ContourExample
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
Fury said to a mouse that he met in the house, let us both go to law, I will prosecute you. Come, I will take no denial, we must have a trial, for really this morning I have nothing to do. Said the mouse to the cur, such a trial, dear sir, with no jury or judge, would be wasting our breath.
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
Fury said to a mouse that he met in the house, let us both go to law, I will prosecute you. Come, I will take no denial, we must have a trial, for really this morning I have nothing to do. Said the mouse to the cur, such a trial, dear sir, with no jury or judge, would be wasting our breath.
The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over.
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/contour.jsonUsage
import { Contour } from "@/components/ui/contour"const coda ="The last bar is not the end of the piece. The players hold still, the bows stay on the strings, and for a moment nobody in the hall moves, because the silence after the music is still part of it. Then someone breathes, and it is over."const tale ="Fury said to a mouse that he met in the house, let us both go to law, I will prosecute you. Come, I will take no denial, we must have a trial, for really this morning I have nothing to do. Said the mouse to the cur, such a trial, dear sir, with no jury or judge, would be wasting our breath."export default function Example() {return (<div className="grid gap-16"><Contour fade className="ot-mp max-w-[36rem]">{coda}</Contour><div className="grid grid-cols-[minmax(0,1fr)] items-start gap-12 sm:grid-cols-2"><Contour variant="tale" fade least={0.4} className="ot-mp">{tale}</Contour><Contour variant="cola" className="ot-mp">{coda}</Contour></div></div>)}Contract
- Underneath: native, plus a hook that lays the lines out with pretext (
@chenglou/pretext, loaded when it's needed). - Anatomy:
<p class="ot-contour" data-shape data-variant="tale | cola">holding the text for readers (.ot-sr) and.ot-contour-lines, one aria-hidden block per line, each carrying--t, how far through the text it starts, and--i, its index. Incolathe lines are grouped by phrase in.ot-contour-colon, a turnover markeddata-turn. - edge (the default, no attribute): a paragraph set to a contour, the way a score draws a swell. Each line is laid to its own width:
diminuendonarrows to the end,crescendoopens towards it,hairpinswells and closes, centred. Every line but the last is then spread to its width, mostly in the word gaps (at most 0.3em a gap) and a little in the letters, and takes one more word when the gaps can close by 0.06em to hold it, so the edge is the hairpin's straight line rather than a ragged stair. The browser never re-wraps them. - tale: after the Mouse's Tale in Alice (1865), where the type shrinks as the tail thins.
shapesets the size of the type instead of the width (leastis the smallest share of the inherited size, never under 10px), and every line holds half the measure in its own size, so the tail narrows as the type does. The lines swing about the middle on a slow wave, one swing every seven lines. Moving a fine pointer across the paragraph moves the wave along (--phase, a full turn across its width) and the lines further down follow later (each line's glide is--ot-andanteplus one--ot-arpeggiofor each line above it), so the tail swishes after the hand; leaving lets it come to rest. Touch and reduced motion get the still tail. Right to left it swings from the other side. - cola: per cola et commata, the way Jerome set scripture to be read aloud. A line to each phrase, broken after a comma, a colon, a full stop, a question or a dash, so the ragged edge is the rhythm of the speech. A phrase longer than the measure turns over, hung 1.5em in (at the inline start, so it mirrors right to left). Pointing at a phrase keeps it in ink while the others rest in pencil (moderato); the paragraph is one reading, so nothing is focusable.
data-fadelets the colour follow the shape, ink where it's loud and pencil where it's quiet, so the quietest line still reads.- It lays out again when its width changes or
<html>changes face (it watches the pair, scheme, mode and key attributes, notclass, which smooth scrolling toggles on every scroll). Before the fonts arrive, or without script, it's a plain paragraph. Forced colours: the text is system text and the pointing colours fall away.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
children | string | The paragraph, as plain text: pretext measures it. | |
shape | "diminuendo" | "crescendo" | "hairpin" | "diminuendo" | Diminuendo narrows to the end, crescendo opens towards it, hairpin swells and closes, centred. For tale it shapes the size of the type instead of the width; cola ignores it. |
least | number | 0.3 | The narrowest a line may be, as a share of the measure. For tale, the smallest size, as a share of the inherited size (never under 10px). |
variant | "edge" | "tale" | "cola" | "edge" | edge: each line but the last is spread to its width, so the contour's edge is a straight line. tale: the Mouse's Tale; each line holds half the measure in its own size, so the tail narrows as the type shrinks, and it winds down the page on a slow wave that the hand across it moves along. cola: a line to each phrase (broken after a comma, colon, full stop or dash), as texts were set to be read aloud; a phrase too long turns over, hung in, and pointing at one keeps it in ink while the rest rest in pencil. |
fade | boolean | false | Let the colour follow the shape: ink where it's loud, pencil where it's quiet. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Contour | The score's hairpin | The lines taper along a straight edge |
| Tale contour | Carroll's Mouse's Tale (1865) | The type shrinks as the tail winds down the page |
| Cola contour | Per cola et commata, text set to be read aloud | A line to each phrase; the edge is the rhythm of speech |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Tale contour | Swish | The tail's wave moves with the hand across it; the lines further down follow later |
| Cola contour | Phrasing | The phrase you point at stays in ink; the others rest in pencil |