motion-font

Animates one or more variable font axes (weight, optical size, width, or any custom axis) using Motion spring physics. Three trigger modes: view (viewport entry), hover (mouse/focus), and scroll (axis tracks scroll position). Requires a variable font with the targeted axes.

Live showcase → Weight spectrum · Word cloud · Spring bounce

Hover: weight

The most common use: bold on hover with a spring-back on leave.

Hover to bold
<motion-font axis="wght" from="300" to="800" trigger="hover">
  Hover to bold
</motion-font>

View: viewport reveal

Animates the axis once when the element scrolls into view. Ideal for hero headings that gain weight on entrance. Set once="false" to replay it every time the element enters the viewport.

Weight reveal
<motion-font axis="wght" from="100" to="900" duration="1.2" trigger="view">
  Weight reveal
</motion-font>

Scroll: tied to position

The axis value maps directly to scroll progress: from when the element enters the viewport, to when it exits. No spring; direct 1:1 mapping. Works best in real page context. See the showcase.

<motion-font axis="wght" from="100" to="900" trigger="scroll">
  Scroll weight
</motion-font>

Multiple axes

Use the axes prop to animate several axes at once. Format: space-separated tag:from:to triplets. Overrides axis, from, and to.

Hover — weight & optical size
<!-- weight + optical size -->
<motion-font axes="wght:200:800 opsz:8:32" trigger="hover">
  Hover me
</motion-font>

Bounce

Control spring overshoot with bounce (0–1). Higher values overshoot and snap back.

Aa bounce=0
Aa bounce=0.3
Aa bounce=0.6
<motion-font axis="wght" from="300" to="800" trigger="hover" bounce="0.3">
  Bouncy bold
</motion-font>

Delay

Delay the animation start in seconds. This is useful for staggering multiple reveals.

First line Second line Third line
<motion-font axis="wght" from="100" to="900" trigger="view" delay="0">First line</motion-font>
<motion-font axis="wght" from="100" to="900" trigger="view" delay="0.15">Second line</motion-font>
<motion-font axis="wght" from="100" to="900" trigger="view" delay="0.3">Third line</motion-font>

Loop

Add loop to spring to to, hold, spring back to from and repeat, like a headline that breathes. hold sets how long it stays at to, gap how long it rests at from.

Breathe
<motion-font axis="wght" from="200" to="800" loop hold="1.2" gap="0.4">Breathe</motion-font>

With trigger="view" the cycle runs only while the text is on screen, and once is ignored. Add pause-on-hover to freeze it while the pointer is over the text.

Properties

Attribute Property Type Default Description
axis axis string 'wght' Font axis tag: 'wght', 'wdth', 'opsz', or any custom axis. Ignored when axes is set.
axes axes string '' Multi-axis: space-separated tag:from:to triplets, e.g. "wght:300:800 opsz:8:32". Overrides axis, from, to.
from from number 300 Starting axis value. Ignored when axes is set.
to to number 700 Target axis value. Ignored when axes is set.
duration duration number 0.6 Spring duration hint in seconds.
bounce bounce number 0 Spring bounciness 0–1. 0 = no overshoot, 1 = very bouncy.
delay delay number 0 Delay before animation starts in seconds.
trigger trigger string 'view' 'view': on viewport entry  |  'hover': on mouseenter / focusin  |  'scroll': axis tracks scroll position
once once boolean true For trigger='view': animate only on the first intersection. Set it to "false" to animate on every entry. Ignored with loop.
loop loop boolean false Spring to to, hold, spring back to from and repeat. With trigger="view" the cycle runs while the text is on screen
hold hold number 1.6 With loop, seconds the text stays at to before it springs back
gap gap number 0.5 With loop, seconds the text rests at from before it springs again
pause-on-hover pauseOnHover boolean false Pause the loop while the pointer is over the text, resuming on leave

Methods

Method Type Default Description
replay() void — Reset axis to from and re-run the entrance animation. Only has effect for trigger="view".

Variable font required: the targeted axis must exist in the loaded font. Inter supports wght (100–900) and opsz (14–32). For wdth use a font like Roboto Flex.

JavaScript API

Trigger the font programmatically with play() and cancel().

JS API
–

Accessibility

Respects prefers-reduced-motion: when enabled, axis animations are skipped, the font renders at its target value immediately and loop never starts.