@liiift-studio/typsettle
v1.0.19
Published
Page-load animation where per-line tracking starts at random offsets and eases to optical equilibrium — like watching a compositor tune a paragraph
Downloads
1,158
Readme
Typsettle
Paragraph text enters from randomised letter-spacing and transitions to optical equilibrium. A page-load animation that feels typographic rather than decorative — lines staggered, motion purposeful. Like watching a compositor tune a paragraph. Respects prefers-reduced-motion.
typsettle.com · npm · GitHub
TypeScript · Zero dependencies · React + Vanilla JS
Install
npm install @liiift-studio/typsettleUsage
Next.js App Router: this library uses browser APIs. Add
"use client"to any component file that imports from it.
React component
import { SettleText } from '@liiift-studio/typsettle'
<SettleText spread={0.04} duration={800} stagger={80}>
Your paragraph text here...
</SettleText>React hook
import { useSettle } from '@liiift-studio/typsettle'
// Inside a React component:
const { ref, replay } = useSettle({ spread: 0.04, duration: 800, stagger: 80 })
return <p ref={ref}>{children}</p>Vanilla JS
import { applySettle, removeSettle, replaySettle, getCleanHTML } from '@liiift-studio/typsettle'
const el = document.querySelector('p')
const original = getCleanHTML(el)
applySettle(el, original, { spread: 0.04, duration: 800, stagger: 80 })
// Re-run after custom fonts load — line detection uses BCR, which gives wrong
// line groups if the font hasn't swapped in yet. applySettle resets to original first,
// so re-calling it is safe:
document.fonts.ready.then(() => {
applySettle(el, original, { spread: 0.04, duration: 800, stagger: 80 })
})
// The line spans remain in the DOM after the animation completes.
// Call removeSettle to restore original markup (e.g. before re-running):
// removeSettle(el, original)
// Replay the settle animation on a previously-settled element:
// replaySettle(el)TypeScript
import type { SettleOptions } from '@liiift-studio/typsettle'
const opts: SettleOptions = { spread: 0.04, duration: 800, stagger: 80, active: true }Options
| Option | Default | Description |
|--------|---------|-------------|
| spread | 0.04 | Max initial letter-spacing offset in em. Each line gets a random value in [-spread, +spread] |
| duration | 800 | CSS transition duration in ms |
| easing | 'cubic-bezier(0.25, 0.1, 0.25, 1)' | CSS easing string |
| stagger | 0 | Delay between lines in ms. 0 settles all lines together; 80 gives a cascading effect |
| active | true | Set false to skip the animation entirely (e.g. for conditional disabling) |
| targetTracking | 0 | Letter-spacing each line settles to in em. 0 = natural spacing. 'auto' measures the original rendered tracking and settles there |
| direction | 'expand' | 'expand' animates from condensed → normal tracking; 'compress' animates from normal → condensed |
| intersect | false | Replay the animation each time the element scrolls into view |
| quietReplay | false | When true, replays with each line individually offsetting then settling (staggered), instead of all lines flashing simultaneously. Has no effect when stagger is 0 |
| lineDetection | 'bcr' | 'bcr' reads actual browser layout — ground truth, works with any font and inline HTML. 'canvas' uses @chenglou/pretext for arithmetic line breaking with no forced reflow on resize (npm install @chenglou/pretext). Falls back to 'bcr' while pretext loads |
| as | 'p' | HTML element to render, e.g. 'h1', 'div'. (React component only) |
API reference
Core functions
| Function | Description |
|----------|-------------|
| applySettle(element, originalHTML, options) | Wrap lines and run the settle animation |
| removeSettle(element, originalHTML) | Restore the element to its original markup |
| replaySettle(element) | Replay the settle animation on a previously-settled element |
| getCleanHTML(element) | Return the element's inner HTML with any Typsettle spans stripped |
React hook
useSettle(options) returns { ref, replay }:
| Key | Type | Description |
|-----|------|-------------|
| ref | React.RefObject | Attach to the element you want to animate |
| replay | () => void | Call to replay the settle animation imperatively |
SettleText component props
Accepts all SettleOptions plus:
| Prop | Type | Description |
|------|------|-------------|
| as | string | HTML element to render (default 'p') |
| onReady | (replay: () => void) => void | Callback fired once the animation completes, receiving a replay function |
How it works
Each visual line is wrapped in a <span>. A random letter-spacing value in [-spread, +spread] em is applied immediately. On the next requestAnimationFrame, a CSS transition is set on each span and letter-spacing is set to 0em — the browser animates each line back to zero. Stagger is implemented as a per-span transition-delay of i × stagger ms.
The line spans are not automatically removed after the transition completes — they remain in the DOM with letter-spacing: 0em. Call removeSettle(el, original) manually if you need to restore the original markup (e.g. before a re-run). The animation is skipped entirely if prefers-reduced-motion: reduce is set or active is false.
Line break safety: Line breaks are locked to the browser's natural layout. Each run starts from the original HTML, detects lines at zero letter-spacing, then wraps them with white-space: nowrap. Word breaks never change during or after the animation. Lines may overflow briefly during the transition (when random offsets are applied) but settle to letter-spacing: 0em — their exact natural width — by the time the animation ends.
Dev notes
next in root devDependencies
package.json at the repo root lists next as a devDependency. This is a Vercel detection workaround — not a real dependency of the npm package. Vercel's build system inspects the root package.json to detect the framework; without next present it falls back to a static build and skips the Next.js pipeline, breaking the /site subdirectory deploy.
The package itself has zero runtime dependencies. Do not remove this entry.
Future improvements
- Variable font axis settle — settle
wdthorwghtinstead of (or alongside) letter-spacing, for fonts where axis variation reads more clearly at large sizes - Random seed — accept a
seedoption for deterministic random offsets, so SSR-rendered markup matches the client hydration state
Current version: 1.0.17
