textbreath
The paragraph
breathes.
Each line of a paragraph oscillates its letter-spacing — or variable font axis — at a phase offset from its neighbours. Two modes: phase gives each line a fixed ripple; tide sends a traveling wave through the paragraph. At low amplitudes it reads as living rather than animated.
Live demo — watch the paragraph
How it works
Phase mode
Each visual line is assigned a fixed phase offset. The wave function is evaluated at each line's phase every frame. Lines oscillate in place at staggered positions in the cycle — a standing ripple rather than a wave that moves.
Tide mode
A wave travels through the paragraph from top to bottom (or bottom to top). Each line's phase advances with time and its position in the paragraph — the same wave that passes through floodText, but applied to letter-spacing or a variable font axis.
Usage
Drop-in component
import { BreatheText } from '@liiift-studio/textbreath'
<BreatheText amplitude={0.012} period={3.5} phaseOffset={0.785}>
Your paragraph text here...
</BreatheText>Hook
import { useBreathe } from '@liiift-studio/textbreath'
const ref = useBreathe({ amplitude: 0.012, period: 3.5, phaseOffset: 0.785 })
<p ref={ref}>{children}</p>Vanilla JS
import { applyBreathe, startBreathe, removeBreathe, getCleanHTML } from '@liiift-studio/textbreath'
const el = document.querySelector('p')
const original = getCleanHTML(el)
const { lineSpans } = applyBreathe(el, original, { amplitude: 0.012, period: 3.5 })
const stop = startBreathe(lineSpans, { amplitude: 0.012, period: 3.5 })
// Later — stop animation and restore:
stop()
removeBreathe(el, original)Options
| Option | Default | Description |
|---|---|---|
| amplitude | 0.012 | Peak change per cycle. Em for letter-spacing; scaled by 100/400 for wdth/wght. |
| period | 3.5 | Seconds per full oscillation cycle. |
| phaseOffset | π/4 | Phase shift between adjacent lines in radians. Used in phase mode only. |
| waveShape | 'sine' | 'sine' | 'triangle' |
| axis | 'letter-spacing' | 'letter-spacing' | 'wdth' | 'wght' |
| mode | 'phase' | 'phase' = standing ripple per line, 'tide' = wave travels through paragraph. |
| direction | 'down' | Tide travel direction. 'down' | 'up'. Used in tide mode only. |
| 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. |