II Type

Star on GitHubFeedback
II TypeNative HTML and a hookot-contour

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 Contour

Example

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.

Diminuendo

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.

Crescendo

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.

Hairpin

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.

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.

Cola

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.json

Usage

contour-example.tsx
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. In cola the lines are grouped by phrase in .ot-contour-colon, a turnover marked data-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: diminuendo narrows to the end, crescendo opens towards it, hairpin swells 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. shape sets the size of the type instead of the width (least is 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-andante plus one --ot-arpeggio for 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-fade lets 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, not class, 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

Props of Contour
PropTypeDefaultWhat it does
childrenstringThe 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.
leastnumber0.3The 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.
fadebooleanfalseLet the colour follow the shape: ink where it's loud, pencil where it's quiet.

Craft

Where its move comes from

Where its move comes from
ComponentReferenceMove
ContourThe score's hairpinThe lines taper along a straight edge
Tale contourCarroll's Mouse's Tale (1865)The type shrinks as the tail winds down the page
Cola contourPer cola et commata, text set to be read aloudA line to each phrase; the edge is the rhythm of speech

How it moves

How it moves
ComponentArticulationWhat moves
Tale contourSwishThe tail's wave moves with the hand across it; the lines further down follow later
Cola contourPhrasingThe phrase you point at stays in ink; the others rest in pencil