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.