32 CSS Timelines
20 / 32

All 32 in 32 CSS Timelines

  1. 01GitHub-Style Commit History Timeline
  2. 02CI/CD Deploy Pipeline Timeline — Status Steps with Live Spinner
  3. 03Resume / Work Experience Timeline with Subgrid Alignment
  4. 04Order Tracking Timeline — Delivery Progress Stepper
  5. 05Fitness Activity Feed Timeline — Workout Cards with Metric Grids
  6. 06Scroll-Driven Reveal Timeline — animation-timeline: view() Entrances
  7. 07Team Swimlane Gantt Timeline — Grid-Line Placement, No Libraries
  8. 08Conference Agenda Timeline — Hour Grid with Parallel Tracks
  9. 09Stratigraphy Timeline — Geological Layer Blocks with Depth Scale
  10. 10Concert Tour Timeline — Ticket Stub Cards with Perforated Edges
  11. 11Patient Vitals Timeline — Medical Dashboard with CSS Sparklines
  12. 12Film Reel Timeline — Sprocket-Hole Border History Strip
  13. 13Mission Control Timeline — T-Minus Event Log with Status Dots
  14. 14Fermentation Calendar Timeline — Process Stages as Duration Bars
  15. 15Conversation Thread Timeline — Nested Replies with Curved Connectors
  16. 16Accordion Timeline — Expandable Milestones with Exclusive Open
  17. 17Cinematic Filmstrip Timeline — Scroll-Snap Career Frames
  18. 18Nature Trail Timeline — Winding Dashed Path with Waypoint Badges
  19. 19Terminal Log Timeline — Command-Line Session with Typing Caret
  20. 20Reading-Progress Timeline — animation-timeline: scroll() Article Tracker
  21. 21Zigzag Alternating Timeline — Container Query Responsive
  22. 22Tabbed Era Timeline — Radio-Button Period Switcher, Zero JS
  23. 23Anchor-Nav Timeline — :target Deep-Linkable Milestones
  24. 24Scrollytelling Timeline — Sticky Chapter Panel with Scrolling Beats
  25. 25Product Roadmap Timeline — Quarter Columns with Status Chips
  26. 26Horizontal Panorama Timeline — Vertical Scroll Drives Sideways Travel
  27. 27SaaS Changelog Timeline — Version Badges with Sticky Month Rail
  28. 28Editorial History Timeline — Sticky Era Headers in Serif
  29. 29Comparison Dual Timeline — Two Histories on One Spine
  30. 30Metro Map Timeline — Transit Lines with Interchange Stations
  31. 31Flight Itinerary Timeline — Segments, Layover and Timezone Footnotes
  32. 32Live Activity Feed Timeline — Real-Time Events with :has() Filter
Pure CSSMIT licensed

Reading-Progress Timeline — animation-timeline: scroll() Article Tracker

A long-read companion: a progress bar that fills as the article scrolls and a chapter rail whose dots light up as each section passes — both driven by the CSS scroll() and view() timelines, the pure-CSS replacement for the scroll-listener progress bars every publisher ships in JavaScript today.

Live Demo
Try it

Build interactive UI like this from scratch: 20 HTML, CSS & JavaScript projects

ZTM · JavaScript Web Projects · 30‑day money-back guarantee · Affiliate link

10% off at checkout with code
Check ZTM Course

The code

<section class="tls-20" aria-label="Reading progress timeline demo">
  <div class="tls-20__frame">
    <div class="tls-20__scroller" tabindex="0">
      <header class="tls-20__bar">
        <p class="tls-20__label">The State of CSS · 12 min read</p>
        <div class="tls-20__track" aria-hidden="true"><div class="tls-20__fill"></div></div>
      </header>
      <article class="tls-20__article">
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>1 · The layout wars are over</h4>
          <p>Grid and flexbox settled the argument a decade ago, and the last float-based holdouts have quietly refactored. What remains is choosing the right tool: one-dimensional flow to flexbox, two-dimensional structure to grid, and — since subgrid — nested structure to the parent's tracks.</p>
          <p>The practical consequence is that layout bugs have become placement bugs. We no longer fight the engine; we mis-declare our intent to it. That is enormous progress.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>2 · Components query themselves</h4>
          <p>Container queries ended the era of the viewport as the only breakpoint. A card now reshapes for the sidebar it lives in, not the monitor it happens to be displayed on — and design systems finally ship components that are honestly portable.</p>
          <p>The mental shift matters more than the syntax: responsive design becomes a property of the component, not of the page.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>3 · The scrollbar becomes a timeline</h4>
          <p>Scroll-driven animations moved an entire genre of JavaScript — progress bars, reveal-on-scroll, parallax — into declarative CSS that runs off the main thread. This very progress bar is one of them: no listener, no rAF loop, no jank budget.</p>
          <p>The rule of thumb: if an effect is a pure function of scroll position, it belongs to the compositor now.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>4 · What to learn next</h4>
          <p>:has() for relational styling, view transitions for state changes, anchor positioning for tooltips and menus. None of them are exotic anymore — they are simply what CSS is.</p>
        </section>
      </article>
    </div>
  </div>
</section>
.tls-20,
.tls-20 *,
.tls-20 *::before,
.tls-20 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-20 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #c2410c;
  --tl-line: #f3e5dc;
  --tl-ink: #2b1d14;
  --tl-mut: #8a7263;
  font-family: Georgia,'Times New Roman',serif;
  background: #fdf9f6;
  padding: 64px 24px;
  display: grid;
  place-items: center;
}

.tls-20__frame {
  width: min(620px,100%);
}

.tls-20__scroller {
  height: 420px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--tl-line);
  border-radius: 16px;
  box-shadow: 0 10px 34px -22px rgb(43 29 20 / .3);
}

.tls-20__bar {
  position: sticky;
  top: 0;
  z-index: 2;
  background: #fff;
  padding: 14px 26px 0;
  border-bottom: 1px solid var(--tl-line);
}

.tls-20__label {
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tl-mut);
  padding-bottom: 10px;
}

.tls-20__track {
  height: 4px;
  background: var(--tl-line);
  border-radius: 2px 2px 0 0;
  overflow: hidden;
}

.tls-20__fill {
  height: 100%;
  background: var(--tl-accent);
  transform-origin: left;
}

.tls-20__article {
  padding: 10px 26px 30px;
}

.tls-20__sec {
  padding: 18px 0 10px;
}

.tls-20__h {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 14px;
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  margin-bottom: 10px;
}

.tls-20__dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2.5px solid var(--tl-accent);
  background: transparent;
}

.tls-20__sec p {
  font-size: 15.5px;
  line-height: 1.7;
  color: #4a3a2e;
  margin-bottom: 12px;
  text-wrap: pretty;
}

@supports (animation-timeline: scroll()) {
  .tls-20__fill {
    transform: scaleX(0);
    animation: tls-20-grow linear both;
    animation-timeline: scroll(nearest);
  }

  @keyframes tls-20-grow {
    to {
      transform: scaleX(1);
    }
  }

  .tls-20__sec {
    view-timeline-name: --tls25-sec;
  }

  .tls-20__dot {
    animation: tls-20-dot linear both;
    animation-timeline: view(nearest);
    animation-range: entry 0% entry 100%;
  }

  @keyframes tls-20-dot {
    to {
      background: var(--tl-accent);
    }
  }
}
/* No JavaScript — the bar is animation-timeline: scroll(nearest) with a scaleX fill (compositor-only); chapter dots fill against per-section view() timelines. Remove the demo's fixed-height scroller and the same CSS tracks the page. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Timeline from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Reading-Progress Timeline — animation-timeline: scroll() Article Tracker
Source: https://codefronts.com/layouts/css-timelines/art-deco-steps/

A long-read companion: a progress bar that fills as the article scrolls and a chapter rail whose dots light up as each section passes — both driven by the CSS scroll() and view() timelines, the pure-CSS replacement for the scroll-listener progress bars every publisher ships in JavaScript today.
## HTML
```html
<section class="tls-20" aria-label="Reading progress timeline demo">
  <div class="tls-20__frame">
    <div class="tls-20__scroller" tabindex="0">
      <header class="tls-20__bar">
        <p class="tls-20__label">The State of CSS · 12 min read</p>
        <div class="tls-20__track" aria-hidden="true"><div class="tls-20__fill"></div></div>
      </header>
      <article class="tls-20__article">
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>1 · The layout wars are over</h4>
          <p>Grid and flexbox settled the argument a decade ago, and the last float-based holdouts have quietly refactored. What remains is choosing the right tool: one-dimensional flow to flexbox, two-dimensional structure to grid, and — since subgrid — nested structure to the parent's tracks.</p>
          <p>The practical consequence is that layout bugs have become placement bugs. We no longer fight the engine; we mis-declare our intent to it. That is enormous progress.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>2 · Components query themselves</h4>
          <p>Container queries ended the era of the viewport as the only breakpoint. A card now reshapes for the sidebar it lives in, not the monitor it happens to be displayed on — and design systems finally ship components that are honestly portable.</p>
          <p>The mental shift matters more than the syntax: responsive design becomes a property of the component, not of the page.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>3 · The scrollbar becomes a timeline</h4>
          <p>Scroll-driven animations moved an entire genre of JavaScript — progress bars, reveal-on-scroll, parallax — into declarative CSS that runs off the main thread. This very progress bar is one of them: no listener, no rAF loop, no jank budget.</p>
          <p>The rule of thumb: if an effect is a pure function of scroll position, it belongs to the compositor now.</p>
        </section>
        <section class="tls-20__sec">
          <h4 class="tls-20__h"><span class="tls-20__dot" aria-hidden="true"></span>4 · What to learn next</h4>
          <p>:has() for relational styling, view transitions for state changes, anchor positioning for tooltips and menus. None of them are exotic anymore — they are simply what CSS is.</p>
        </section>
      </article>
    </div>
  </div>
</section>
```
## CSS
```css
.tls-20,
.tls-20 *,
.tls-20 *::before,
.tls-20 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-20 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #c2410c;
  --tl-line: #f3e5dc;
  --tl-ink: #2b1d14;
  --tl-mut: #8a7263;
  font-family: Georgia,'Times New Roman',serif;
  background: #fdf9f6;
  padding: 64px 24px;
  display: grid;
  place-items: center;
}

.tls-20__frame {
  width: min(620px,100%);
}

.tls-20__scroller {
  height: 420px;
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--tl-line);
  border-radius: 16px;
  box-shadow: 0 10px 34px -22px rgb(43 29 20 / .3);
}

.tls-20__bar {
  position: sticky;
  top: 0;
  z-index: 2;
  background: #fff;
  padding: 14px 26px 0;
  border-bottom: 1px solid var(--tl-line);
}

.tls-20__label {
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tl-mut);
  padding-bottom: 10px;
}

.tls-20__track {
  height: 4px;
  background: var(--tl-line);
  border-radius: 2px 2px 0 0;
  overflow: hidden;
}

.tls-20__fill {
  height: 100%;
  background: var(--tl-accent);
  transform-origin: left;
}

.tls-20__article {
  padding: 10px 26px 30px;
}

.tls-20__sec {
  padding: 18px 0 10px;
}

.tls-20__h {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 14px;
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  margin-bottom: 10px;
}

.tls-20__dot {
  flex: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2.5px solid var(--tl-accent);
  background: transparent;
}

.tls-20__sec p {
  font-size: 15.5px;
  line-height: 1.7;
  color: #4a3a2e;
  margin-bottom: 12px;
  text-wrap: pretty;
}

@supports (animation-timeline: scroll()) {
  .tls-20__fill {
    transform: scaleX(0);
    animation: tls-20-grow linear both;
    animation-timeline: scroll(nearest);
  }

  @keyframes tls-20-grow {
    to {
      transform: scaleX(1);
    }
  }

  .tls-20__sec {
    view-timeline-name: --tls25-sec;
  }

  .tls-20__dot {
    animation: tls-20-dot linear both;
    animation-timeline: view(nearest);
    animation-range: entry 0% entry 100%;
  }

  @keyframes tls-20-dot {
    to {
      background: var(--tl-accent);
    }
  }
}
```

## JavaScript
```js
/* No JavaScript — the bar is animation-timeline: scroll(nearest) with a scaleX fill (compositor-only); chapter dots fill against per-section view() timelines. Remove the demo's fixed-height scroller and the same CSS tracks the page. */
```

How this works

The article lives in its own scroller (overflow-y:auto with a fixed height — in production this is simply the page). The progress bar declares animation: scaleX 0→1; animation-timeline: scroll(nearest) — its fill is mapped to the scroller's scroll range, updating on the compositor with zero main-thread work. Compare that to the traditional scroll-listener version, which runs JS on every frame and janks precisely when the page is busiest.

The chapter dots use the companion API: each section declares view-timeline-name: --tls25-sec, and its dot (a sibling-referenced element inside the section header) animates its fill against animation-timeline: view() with animation-range: entry 0% entry 100% — the dot pours full as its chapter enters the reading zone, and drains if you scroll back. Progress becomes spatial, bidirectional and truthful, because it IS the scroll position rather than a debounced estimate of it.

The whole enhancement sits inside @supports (animation-timeline: scroll()): unsupported browsers get the identical article with a static rail — nothing breaks, nothing hides. transform: scaleX (not width) keeps the bar's animation compositor-only.

Techniques used: animation-timeline: scroll(nearest) progress fill · scaleX transform fill (compositor-only, never width) · per-section view() timelines filling chapter dots · animation-range entry mapping · @supports progressive enhancement · sticky in-scroller header housing the bar

Make it yours

  • Track the PAGE instead of a demo box: remove the fixed height/overflow from the scroller and the same CSS tracks the root scroller.
  • Fill origin: transform-origin left — center gives a bar growing outward from the middle.
  • Chapter granularity: any element can own a view-timeline; track h2s in prose without restructuring.
  • Bar thickness/color: two declarations on the fill; it inherits the accent token.

Gotchas — read before shipping

  • scroll(nearest) finds the nearest scrollable ANCESTOR — if a wrapper gains overflow for any reason, the bar starts tracking the wrong scroller. scroll(root) pins it to the page explicitly.
  • Animate scaleX, not width — width invalidates layout every scroll frame and defeats the entire point of compositor-driven timelines.
  • The @supports gate must wrap the initial scaleX(0) too, or unsupported browsers render a permanently empty bar.
  • A progress bar inside an inner scroller needs the bar INSIDE the scroller (sticky), not outside it — outside, scroll(nearest) resolves to the wrong element.

Browser support

ChromeSafariFirefoxEdge
117+26+not yet117+

Floor set by :has(), animation-timeline, subgrid, text-wrap as this demo is written. The core technique itself goes back further — Chrome 115+.

Scroll-driven animations are Baseline newly-available 2025; the @supports gate leaves other browsers with the identical static article.

Techniques used in this demo

Search CodeFronts

Loading…