5 min read
0%

Scroll-Driven Variable Fonts

Back to Blog
Scroll-Driven Variable Fonts

Scroll-Driven Variable Fonts

Scroll-driven animations and variable fonts are a natural pair. Axis values like wght, wdth, ital, and custom axes respond to scroll position with zero JavaScript — the browser drives the animation directly from the scroll timeline.

@keyframes fatten {
  from {
    font-variation-settings: "wght" 300;
  }
  to {
    font-variation-settings: "wght" 900;
  }
}

h1 {
  animation: fatten linear;
  animation-timeline: scroll();
  animation-range: entry 0% entry 60%;
}

The heading weight increases from 300 to 900 as it enters the viewport — no JS, no IntersectionObserver.

Scroll Timelines

Two timeline types drive scroll-based font animations:

/* Scroll progress of the nearest scrollable ancestor */
animation-timeline: scroll();
animation-timeline: scroll(root block); /* explicit scroller and axis */

/* Element's position relative to the viewport */
animation-timeline: view();
animation-timeline: view(block 10% 90%); /* with inset */

scroll() ties the animation to how far the container has scrolled. view() ties it to how much of the element is visible.

Animating Multiple Axes Simultaneously

Variable fonts can have several axes. Animate them together for richer effects:

@keyframes condense-on-scroll {
  from {
    font-variation-settings:
      "wght" 700,
      "wdth" 100,
      "opsz" 12;
  }
  to {
    font-variation-settings:
      "wght" 300,
      "wdth" 60,
      "opsz" 72;
  }
}

.hero-title {
  animation: condense-on-scroll linear both;
  animation-timeline: scroll(root);
  animation-range: 0px 300px;
}

As the page scrolls 300px, the title gets lighter, narrower, and shifts to a larger optical size.

Named Scroll Timelines

For complex layouts with multiple animated elements sharing one scroll position:

.scroll-container {
  overflow-y: auto;
  scroll-timeline: --main-scroll block;
}

.label {
  animation: label-weight linear both;
  animation-timeline: --main-scroll;
  animation-range: entry 20% entry 80%;
}

.subtitle {
  animation: subtitle-width linear both;
  animation-timeline: --main-scroll;
  animation-range: entry 40% entry 100%;
}

Both elements respond to the same scroll container but with different ranges.

View Timeline for Per-Element Entry

@keyframes reveal-weight {
  from {
    font-variation-settings: "wght" 100;
    opacity: 0;
  }
  to {
    font-variation-settings: "wght" 800;
    opacity: 1;
  }
}

.section-heading {
  animation: reveal-weight linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}

Each .section-heading independently animates as it enters the viewport — no shared counter or scroll offset calculation needed.

Combining with font-weight (for non-variable fallback)

@supports (animation-timeline: scroll()) and
  (font-variation-settings: "wght" 400) {
  h2 {
    animation: heading-weight linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 30%;
  }
}

Wrap in @supports to keep a static font-weight for browsers without variable font or scroll timeline support.

Custom Axes

Variable fonts with custom axes (like CASL for Recursive, or SOFT for custom typefaces) animate the same way:

@keyframes casual-scroll {
  from {
    font-variation-settings:
      "CASL" 0,
      "MONO" 0;
  }
  to {
    font-variation-settings:
      "CASL" 1,
      "MONO" 1;
  }
}

.tagline {
  font-family: "Recursive", sans-serif;
  animation: casual-scroll linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

Respecting Reduced Motion

@media (prefers-reduced-motion: no-preference) {
  .animated-heading {
    animation: fatten linear both;
    animation-timeline: view();
  }
}

Only animate when the user hasn’t opted out of motion.

Browser Support

Scroll-driven animations shipped in Chrome 115 and Firefox 110. Safari 18+ added support. Combine @supports (animation-timeline: scroll()) with a static font-weight fallback for older browsers.


Browser support snapshot

Live support matrix for css-scroll-driven-animations from Can I Use.

Show static fallback image Data on support for css-scroll-driven-animations across major browsers from caniuse.com

Source: caniuse.com

Canvas is not supported in your browser