reflow-free hover weight
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 reflows. It’s jarring, and CSS has no built-in fix: across 15 popular variable fonts, bold made navigation labels a median 4.8% wider.
How we fix it
Canvas measureText gives the advance width of the text at both weights, off screen. The difference, divided by the number of characters, becomes a negative letter-spacing applied on hover, so the width holds. Measure the whole element, each word, or each line in proximity mode. It approximates a grade axis for fonts that only have weight.
The research
Weight Without Width — a talk and paper on why interaction states should change emphasis, not layout.
Usage
Drop-in component
import { BoldLockText } from '@overpunch/hoverboldly'
<BoldLockText
normalWeight={300}
hoverWeight={700}
mode="word"
>
Hover over this text...
</BoldLockText>Hook
import { useBoldLock } from '@overpunch/hoverboldly'
const ref = useBoldLock({ normalWeight: 300, hoverWeight: 700 })
<p ref={ref}>{children}</p>Vanilla JS
import { applyBoldLock } from '@overpunch/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). |
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-hoverboldly. Configure it with data-* attributes.
<!-- Site Settings → Custom Code → Footer, or an Embed element -->
<script src="https://cdn.jsdelivr.net/npm/@overpunch/hoverboldly/dist/hoverboldly.webflow.min.js"></script>
<!-- Then add data-hoverboldly to any text element -->
<h1 data-hoverboldly>Your headline</h1>Framer
Insert → Code → New Component, then paste HoverBoldly.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/@overpunch/hoverboldly"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).