letter-spacing settling
Settle,
into place.
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. Respects prefers-reduced-motion and the active option.
Live demo
How it works
The entrance
Each line of text starts with a randomised letter-spacing offset — some wider, some tighter. The offset is unique per line, creating a sense of subtle chaos that reads as typographic texture rather than noise.
The resolution
A CSS transition carries each line to its natural letter-spacing baseline — the element's existing CSS tracking, if any, or zero if none is set. The stagger control spaces these transitions apart so lines settle in sequence rather than all at once.
Usage
Drop-in component
import { SettleText } from '@liiift-studio/typsettle'
<SettleText spread={0.04} duration={800} stagger={80}>
Your paragraph text here...
</SettleText>Hook
import { useSettle } from '@liiift-studio/typsettle'
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 })
// Replay the animation on a settled element (e.g. on a button click):
// replaySettle(el)
// Restore original markup:
// removeSettle(el, original)Options
| Option | Default | Description |
|---|---|---|
| spread | 0.04 | Max initial letter-spacing offset in em. |
| duration | 800 | Transition duration in milliseconds. |
| easing | 'cubic-bezier(0.25, 0.1, 0.25, 1)' | CSS easing string. |
| stagger | 0 | Delay between lines in ms. 0 = all settle together. |
| active | true | Set false to skip animation entirely. |
| targetTracking | — | Letter-spacing each line settles to, in em. When omitted, lines settle to 0em (the element's natural spacing). Pass a number for a consistent loose or tight equilibrium. 'auto' measures per-line optical density and adjusts each line's target to equalise density across the paragraph — dense lines get more tracking, sparse lines less, clamped to ±0.05em. |
| direction | 'expand' | 'expand' — lines start wide and ease to natural. 'compress' — lines start at zero tracking and ease outward. |
| intersect | false | When true, re-runs the animation each time the element scrolls into view. |
| quietReplay | false | When true, replays stagger each line individually (offset then settle) instead of flashing all lines 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. Install pretext separately. |
no-code
Use it in Webflow, Framer & Figma
The same effect, no build step — drop it straight into your design tool.
Webflow
One script tag, then mark any element with data-typsettle. Configure it with data-* attributes.
<!-- Site Settings → Custom Code → Footer, or an Embed element -->
<script src="https://cdn.jsdelivr.net/npm/@liiift-studio/typsettle/dist/typsettle.webflow.min.js"></script>
<!-- Then add data-typsettle to any text element -->
<h1 data-typsettle>Your headline</h1>Framer
Insert → Code → New Component, then paste Typsettle.tsx ↗. It imports the core from esm.sh and exposes every option in the property panel — no build step.
import { /* core */ } from "https://esm.sh/@liiift-studio/typsettle"Figma · beta
Part of the Type Tools Figma plugin ↗ — Plugins → Development → Import plugin from manifest, run Type Tools, and pick this tool. Here it bakes a single static frame of the animated effect (no live animation in Figma).