Woodtype Flush Block
A woodtype flush heading — each line of a multi-line heading is solved onto the variable font's wdth axis so it stretches flush to both edges of the container, and on reveal the lines spring open from an extremely narrow width to the solved width, one after another.
npx shadcn@latest add https://webberui.com/r/woodtype-flush-block.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<WoodtypeFlushBlock />
Installation
npx shadcn@latest add https://webberui.com/r/woodtype-flush-block.jsonOr, once registries are configured in components.json, install it as @webberui/woodtype-flush-block.
Usage
import { WoodtypeFlushBlock } from "@/components/ui/woodtype-flush-block";
<WoodtypeFlushBlock
text={"THE\nWOODTYPE\nREVIVAL"}
className="font-black uppercase leading-[0.92]"
lineClassName="text-6xl"
/>Split lines with the \n newline character, or pass an array through lines instead:
<WoodtypeFlushBlock
lines={["THE", "WOODTYPE", "REVIVAL"]}
className="font-black uppercase"
/>For the wdth axis to genuinely take effect, the container's typeface must be a variable font with a width (wdth) axis (for example Roboto Flex, Recursive, or Fraunces). If the typeface does not support that axis, the component automatically falls back to horizontal scaling to make up the difference, and still keeps the flush alignment and the spring-open effect.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | — | Heading text, split into lines by the \n newline character |
lines | string[] | — | Specify each line's text directly (takes precedence over text) |
as | "h1"–"h6" | "p" | "div" | "h2" | The semantic tag to render |
wdthMin | number | 25 | Lower bound of the wdth axis solve (the narrowest value when it cannot fill the width) |
wdthMax | number | 200 | Upper bound of the wdth axis solve (the widest value when it overshoots) |
entryWdth | number | wdthMin | The extremely narrow wdth value the reveal starts from |
entryCompress | number | 0.34 | Additional horizontal compression ratio at the start of the reveal, returning to 1 once sprung open |
delay | number | 0 | Overall delay before the reveal starts (seconds) |
stagger | number | 0.09 | Interval between lines springing open in sequence (seconds) |
duration | number | 0.9 | Duration of one line's spring-open animation (seconds) |
bounce | number | 0.32 | Amount of bounce as a line springs open (0–1) |
once | boolean | true | Plays only the first time the element enters the viewport |
lineClassName | string | — | Appended to each line's class (commonly used to set the font size) |
How it works
- Solving for flush lines: the most captivating thing about woodtype composition is that lines with different character counts are all stretched flush to both edges. The component creates one hidden measurement span per line, measures its actual width at different
wdthvalues, then binary-searches for thewdthvalue that fills the container's content width exactly — a short line (few characters) needs a wider width, a long line (many characters) needs a narrower one, so each line naturally ends up with a differentwdth. - Pixel-level compensation: a variable font's
wdthaxis has its own upper and lower bounds, so an extremely short or long line may not fill or may overshoot on width alone. After solving, the component makes up the last few pixels with a residualscaleXto guarantee every line is truly flush; if the typeface has nowdthaxis at all,scaleXachieves the flush alignment on its own and the effect still holds. - Spring-open reveal: on entering the viewport, each line starts from
entryWdth(an extremely narrow width) plus the extra compression fromentryCompress, like a slender column of woodtype, and springs open to the solved width in sequence according tostagger;bouncecontrols the spring-back feel. The width is driven by the CSS variable--wb-wdth, andfont-variation-settingsreflects its changes live. - Responsive re-solving: a
ResizeObserverwatches the container width, and the solve runs once more afterdocument.fonts.ready(when the web fonts have loaded); when the container changes width, every line smoothly re-justifies. The measurement and solving both complete inside a layout effect before the first paint, avoiding layout flashes. - Typesetting advice: a heavy weight (
font-black), uppercase, and tightleadingbest convey the dense, stacked feel of a woodtype poster; set the font size onlineClassNameand keep shared styles such as weight, tracking, and colour on the container'sclassName.
Accessibility
- When the user has "reduce motion" enabled at the system level, the flush heading is presented directly at its solved width with no spring-open reveal animation
- The complete heading text lives in
sr-onlyso a screen reader reads it once; both the visual layer and the hidden measurement layer arearia-hiddendecorative copies and are never read twice - The heading renders with a semantic tag (
h2by default, adjustable throughas), preserving the document outline hierarchy
Alphabet Ledger
An alphabetical index scroll — a poster-scale initial letter is the sticky anchor of each section, and at the handover the new letter pushes the previous one out with a thin-to-bold weight baton animation.
Bopomofo Ruby Text (React ruby typography component)
A React ruby typography component that annotates Chinese characters with Bopomofo (Zhuyin) or Hanyu Pinyin above them — switch between Bopomofo and Pinyin, highlight a character on hover, adjust the annotation size and spacing. Suited to teaching materials, dictionaries, and children's sites.