hoverboldly
Bold on hover,
Zero layout shift.
TypeScript·Canvas measurement·React + Vanilla JS
Every browser will reflow text when you hover to bold — words push down, lines shift. Hover Boldly measures the exact width difference using Canvas, then compensates with letter-spacing so the line never moves.
Live demo — hover or tap the paragraph
The problem with bold hover
Why text reflows
Bold glyphs are wider. When you change font-weight on hover, every character in the element grows slightly, words push into the next line, and the whole paragraph reflts. It’s jarring and there’s no CSS fix.
How we fix it
Canvas measureText gives us the exact advance width of each line at both weights. The difference becomes a negative letter-spacing compensation applied on hover, so total line width stays identical. One measurement pass on mount, zero reflow on hover.
Usage
Drop-in component
import { BoldLockText } from '@liiift-studio/hoverboldly'
<BoldLockText
normalWeight={300}
hoverWeight={700}
mode="word"
>
Hover over this text...
</BoldLockText>Hook
import { useBoldLock } from '@liiift-studio/hoverboldly'
const ref = useBoldLock({ normalWeight: 300, hoverWeight: 700 })
<p ref={ref}>{children}</p>Vanilla JS
import { applyBoldLock } from '@liiift-studio/hoverboldly'
const el = document.querySelector('p')
const cleanup = applyBoldLock(el, { normalWeight: 300, hoverWeight: 700 })
// Later — removes listeners, resets styles, and in word/proximity modes
// also restores element.innerHTML to its original state:
cleanup()Options
| Option | Default | Description |
|---|---|---|
| normalWeight | computed | Font weight at rest. |
| hoverWeight | 700 | Font weight on hover. |
| transitionDuration | 150 | Transition duration in milliseconds. |
| mode | 'element' | 'element' = whole element bolds on hover. 'word' = individual word hover targets. 'proximity' = weight increases per line based on cursor distance, fading with distance. |
| proximityThreshold | 120 | Distance in px from a line's centre over which weight fades. Only used in 'proximity' mode. |
| resizeObserver | true | Re-measure compensation when the element's size changes (e.g. responsive font-size). |
| axes | — | Additional variable font axes to drive on hover (e.g. slnt, wdth). Each key is an OpenType axis tag with normal/hover values. |
| falseSlant | — | Fake italic via CSS skewX() for fonts without a slnt axis. Provide hoverDeg (and optionally normalDeg). |