Variable Font Scroll
Variable font axes are animatable CSS properties, which means animation-timeline can drive font weight, letter-spacing, and more directly from scroll position.
font-variation-settings is an animatable CSS property. That means @keyframes can target it — and once a property is in a keyframe, animation-timeline can drive it from scroll position. No JavaScript, no font loading tricks, no canvas.
The pattern: a tall outer wrapper creates scroll space, a sticky inner element pins to the viewport, and a view-timeline-name on the outer wrapper drives everything inside. The page scroll is the only scroll that matters.
Scroll progress
As you scroll through the section, weight and tracking animate together — heavier type naturally needs tighter tracking.
Weight.
Follows.
Scroll.
scroll ↓
.outer {
height: 300vh;
view-timeline-name: --section;
view-timeline-axis: block;
}
.sticky {
position: sticky;
top: 0;
}
.heading {
font-variation-settings: 'wght' 300;
animation: weight linear both;
animation-timeline: --section;
animation-range: contain 0% contain 100%;
}
@keyframes weight {
from {
font-variation-settings: 'wght' 300;
letter-spacing: 0.06em;
}
to {
font-variation-settings: 'wght' 700;
letter-spacing: -0.03em;
}
}
animation-range: contain 0% contain 100% runs the animation from the moment the outer section covers the full viewport to the moment it stops — locking the sticky element in place for exactly that duration.
Weight lens
Each word peaks at bold as you scroll through its slice of the range. The keyframe pulses light → heavy → light, and animation-range staggers the peak per item. The result: weight follows wherever you’re looking.
.outer {
height: 500vh;
view-timeline-name: --section;
}
.item {
animation: lens linear both;
animation-timeline: --section;
}
/* Stagger the peak per item */
.item:nth-child(1) { animation-range: contain 0% contain 16%; }
.item:nth-child(2) { animation-range: contain 13% contain 29%; }
/* … */
@keyframes lens {
0%, 100% { font-variation-settings: 'wght' 300; }
50% { font-variation-settings: 'wght' 700; }
}
The 3% overlap between adjacent items means two words are partially animated at the same time during the transition, which feels smoother than a hard cutoff.
Browser support
Both demos use animation-timeline. That requires Safari 18 (iOS 18, September 2024). On older iOS the text renders at the light weight start state and stays there. font-variation-settings itself has been in Safari since version 11, so the type still loads and displays correctly.