CSS view-timeline-name: Broadcast Scroll Progress

The CSS view-timeline-name property is a core feature of the CSS Scroll-driven Animations specification that establishes a named view progress timeline linked to a specific element's position within a scroll container. By assigning a custom identifier (a dashed-ident) to an element, developers can track its intersection with the scrollport and broadcast that progress to other elements across the document. This allows sibling, parent, or completely separate elements to trigger and synchronize their own CSS animations based on the scroll position of the tracked element.

What is view-timeline-name?

The view-timeline-name property converts a DOM element (known as the subject) into a progress tracker. Unlike a standard scroll timeline that tracks the overall scrollbar progress of a container from 0% to 100%, a view timeline tracks the relative progress of a specific subject entering, crossing, and exiting its nearest scrollable ancestor.

The property accepts a custom dashed identifier:

.subject-element {
  view-timeline-name: --subject-timeline;
  view-timeline-axis: block; /* or inline */
}

You can also use the shorthand property view-timeline:

.subject-element {
  view-timeline: --subject-timeline block;
}

How Progress is Broadcasted to Other Elements

By default, a named timeline is scoped to the element that defines it and its descendants. However, CSS provides the timeline-scope property to elevate the visibility of a timeline up the DOM tree, enabling arbitrary elements to read the subject's scroll state.

To broadcast scroll progress from a subject to an external target element:

  1. Define the Timeline on the Subject: Use view-timeline-name on the element whose visibility in the viewport drives the animation.
  2. Elevate Scope on a Common Ancestor: Apply timeline-scope to a parent container that encapsulates both the subject and the target element that will consume the timeline.
  3. Consume the Timeline on the Target: Set animation-timeline on the target element using the declared dashed-ident.

Code Example: Driving a Target Element from a Subject

Consider a scenario where a reading progress bar or a floating indicator updates its position based on when a specific section of an article enters the viewport.

<div class="page-container">
  <div class="progress-indicator"></div>

  <main>
    <section class="content-block">Regular content</section>
    <section class="target-section">Trigger Section</section>
    <section class="content-block">More content</section>
  </main>
</div>
/* Step 1: Elevate timeline scope on the shared ancestor */
.page-container {
  timeline-scope: --section-timeline;
}

/* Step 2: Establish the named view timeline on the subject */
.target-section {
  view-timeline-name: --section-timeline;
  view-timeline-axis: block;
}

/* Step 3: Attach the timeline to the independent element */
.progress-indicator {
  position: fixed;
  top: 0;
  left: 0;
  height: 8px;
  width: 100%;
  background-color: #0070f3;
  transform-origin: left;
  
  animation-name: scale-bar;
  animation-timing-function: linear;
  animation-timeline: --section-timeline;
  animation-range: entry 0% exit 100%;
}

@keyframes scale-bar {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

Controlling the Animation Range

When an element consumes a broadcasted view timeline, its animation progress is mapped by default to the entire journey of the subject passing through the viewport (cover 0% to cover 100%).

You can fine-tune when the receiving animation starts and stops using the animation-range property:

  • entry 0% entry 100%: Animates only while the subject is entering the scrollport.
  • exit 0% exit 100%: Animates only while the subject is exiting the scrollport.
  • contain 0% contain 100%: Animates while the subject is fully contained inside the scrollport.

Through view-timeline-name and timeline-scope, CSS allows decoupled, performant, hardware-accelerated animations across disparate interface elements without the need for JavaScript scroll listeners or IntersectionObserver.