32 CSS Timelines
06 / 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

Scroll-Driven Reveal Timeline — animation-timeline: view() Entrances

The scroll-driven flagship: milestones fade-and-rise into place and their markers pop exactly as they scroll into view — driven entirely by the CSS animation-timeline: view() API, zero IntersectionObserver, zero JavaScript. Scrub the page up and down and the entrances play forwards and backwards like a scrubbing video, because the scrollbar IS the timeline.

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-06" aria-label="Scroll-driven reveal timeline demo">
  <div class="tls-06__inner">
    <p class="tls-06__eyebrow">Scroll to reveal — then scroll back up</p>
    <h3 class="tls-06__title">Product journey</h3>
    <ol class="tls-06__list" role="list">
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2024-03">March 2024</time>
          <h4 class="tls-06__h">First prototype</h4>
          <p class="tls-06__p">A weekend build shared with 12 friends. Eight replied the same day.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2024-09">September 2024</time>
          <h4 class="tls-06__h">Private beta</h4>
          <p class="tls-06__p">400 teams on a waitlist. The onboarding survived all of them.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2025-02">February 2025</time>
          <h4 class="tls-06__h">Public launch</h4>
          <p class="tls-06__p">#1 on launch day. Servers held. Founders did not sleep.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2025-11">November 2025</time>
          <h4 class="tls-06__h">10,000 customers</h4>
          <p class="tls-06__p">Profitability, a team of nine, and the roadmap the users wrote.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2026-06">June 2026</time>
          <h4 class="tls-06__h">Platform v2</h4>
          <p class="tls-06__p">The rewrite nobody noticed — which was exactly the point.</p>
        </div>
      </li>
    </ol>
  </div>
</section>
.tls-06,
.tls-06 *,
.tls-06 *::before,
.tls-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-06 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #6d28d9;
  --tl-line: #e4e0ee;
  --tl-bg: #faf9fc;
  --tl-ink: #241f35;
  --tl-mut: #7a7490;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--tl-bg);
  padding: 72px 24px 96px;
  display: grid;
  place-items: center;
}

.tls-06__inner {
  width: min(560px,100%);
}

.tls-06__eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--tl-accent);
  margin-bottom: 8px;
}

.tls-06__title {
  font-size: clamp(24px,4vw,32px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 36px;
}

.tls-06__list {
  list-style: none;
  display: flex;
  flex-direction: column;
}

.tls-06__item {
  position: relative;
  padding: 0 0 40px 40px;
}

.tls-06__item::after {
  content: '';
  position: absolute;
  left: 7px;
  top: 26px;
  bottom: -8px;
  width: 2px;
  background: var(--tl-line);
}

.tls-06__item:last-child::after {
  display: none;
}

.tls-06__item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 10px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--tl-bg);
  border: 4px solid var(--tl-accent);
  z-index: 1;
}

.tls-06__date {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tl-accent);
}

.tls-06__h {
  font-size: 18px;
  font-weight: 750;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  margin: 6px 0 6px;
}

.tls-06__p {
  font-size: 14px;
  line-height: 1.6;
  color: var(--tl-mut);
  text-wrap: pretty;
}

@supports (animation-timeline: view()) {
  .tls-06__card {
    animation: tls-06-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 65%;
  }

  .tls-06__item::before {
    animation: tls-06-pop linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 65%;
  }

  @keyframes tls-06-rise {
    from {
      opacity: 0;
      transform: translateY(24px);
    }

    to {
      opacity: 1;
      transform: none;
    }
  }

  @keyframes tls-06-pop {
    0% {
      transform: scale(0);
    }

    60% {
      transform: scale(1.35);
    }

    100% {
      transform: scale(1);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .tls-06__card,
        .tls-06__item::before {
      animation: none;
    }
  }
}
/* No JavaScript — entrances are animation-timeline: view() with animation-range: entry 0% entry 65%; the @supports gate owns the hidden start state so unsupported browsers see the finished layout. */
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: Scroll-Driven Reveal Timeline — animation-timeline: view() Entrances
Source: https://codefronts.com/layouts/css-timelines/scroll-driven-reveal-timeline-animation-timeline-view-entrances/

The scroll-driven flagship: milestones fade-and-rise into place and their markers pop exactly as they scroll into view — driven entirely by the CSS animation-timeline: view() API, zero IntersectionObserver, zero JavaScript. Scrub the page up and down and the entrances play forwards and backwards like a scrubbing video, because the scrollbar IS the timeline.
## HTML
```html
<section class="tls-06" aria-label="Scroll-driven reveal timeline demo">
  <div class="tls-06__inner">
    <p class="tls-06__eyebrow">Scroll to reveal — then scroll back up</p>
    <h3 class="tls-06__title">Product journey</h3>
    <ol class="tls-06__list" role="list">
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2024-03">March 2024</time>
          <h4 class="tls-06__h">First prototype</h4>
          <p class="tls-06__p">A weekend build shared with 12 friends. Eight replied the same day.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2024-09">September 2024</time>
          <h4 class="tls-06__h">Private beta</h4>
          <p class="tls-06__p">400 teams on a waitlist. The onboarding survived all of them.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2025-02">February 2025</time>
          <h4 class="tls-06__h">Public launch</h4>
          <p class="tls-06__p">#1 on launch day. Servers held. Founders did not sleep.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2025-11">November 2025</time>
          <h4 class="tls-06__h">10,000 customers</h4>
          <p class="tls-06__p">Profitability, a team of nine, and the roadmap the users wrote.</p>
        </div>
      </li>
      <li class="tls-06__item">
        <div class="tls-06__card">
          <time class="tls-06__date" datetime="2026-06">June 2026</time>
          <h4 class="tls-06__h">Platform v2</h4>
          <p class="tls-06__p">The rewrite nobody noticed — which was exactly the point.</p>
        </div>
      </li>
    </ol>
  </div>
</section>
```
## CSS
```css
.tls-06,
.tls-06 *,
.tls-06 *::before,
.tls-06 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-06 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #6d28d9;
  --tl-line: #e4e0ee;
  --tl-bg: #faf9fc;
  --tl-ink: #241f35;
  --tl-mut: #7a7490;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: var(--tl-bg);
  padding: 72px 24px 96px;
  display: grid;
  place-items: center;
}

.tls-06__inner {
  width: min(560px,100%);
}

.tls-06__eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--tl-accent);
  margin-bottom: 8px;
}

.tls-06__title {
  font-size: clamp(24px,4vw,32px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 36px;
}

.tls-06__list {
  list-style: none;
  display: flex;
  flex-direction: column;
}

.tls-06__item {
  position: relative;
  padding: 0 0 40px 40px;
}

.tls-06__item::after {
  content: '';
  position: absolute;
  left: 7px;
  top: 26px;
  bottom: -8px;
  width: 2px;
  background: var(--tl-line);
}

.tls-06__item:last-child::after {
  display: none;
}

.tls-06__item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 10px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--tl-bg);
  border: 4px solid var(--tl-accent);
  z-index: 1;
}

.tls-06__date {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--tl-accent);
}

.tls-06__h {
  font-size: 18px;
  font-weight: 750;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  margin: 6px 0 6px;
}

.tls-06__p {
  font-size: 14px;
  line-height: 1.6;
  color: var(--tl-mut);
  text-wrap: pretty;
}

@supports (animation-timeline: view()) {
  .tls-06__card {
    animation: tls-06-rise linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 65%;
  }

  .tls-06__item::before {
    animation: tls-06-pop linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 65%;
  }

  @keyframes tls-06-rise {
    from {
      opacity: 0;
      transform: translateY(24px);
    }

    to {
      opacity: 1;
      transform: none;
    }
  }

  @keyframes tls-06-pop {
    0% {
      transform: scale(0);
    }

    60% {
      transform: scale(1.35);
    }

    100% {
      transform: scale(1);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .tls-06__card,
        .tls-06__item::before {
      animation: none;
    }
  }
}
```

## JavaScript
```js
/* No JavaScript — entrances are animation-timeline: view() with animation-range: entry 0% entry 65%; the @supports gate owns the hidden start state so unsupported browsers see the finished layout. */
```

How this works

Each <li> carries a plain keyframe animation (opacity 0→1, translateY(24px)→0) but instead of a duration it declares animation-timeline: view() — the animation's progress is mapped to the item's journey through the viewport, not to a clock. animation-range: entry 0% entry 65% confines the whole effect to the first 65% of the element's entry, so items are fully settled well before they reach reading position.

The markers run a SECOND view-timeline animation on the same element's ::before — a scale pop with an overshoot keyframe at 60% — proving that multiple scroll-driven animations can ride one scroller. Because view() progress is bidirectional, scrolling back up reverses everything for free; the page feels scrubbed rather than triggered, which is precisely what JS observer implementations struggle to fake.

The entire block sits inside @supports (animation-timeline: view()), so browsers without the API simply show the finished layout — content is never hidden behind an entrance that can't play. And because opacity:0 only ever appears INSIDE the @supports gate, there is no flash-of-invisible-content anywhere. A prefers-reduced-motion block inside the gate switches the animations off again for motion-sensitive users.

Techniques used: animation-timeline: view() scroll-driven entrances · animation-range: entry clamping · dual scroll-driven animations per item (content + ::before marker) · @supports progressive enhancement (opacity:0 only inside the gate) · prefers-reduced-motion override inside the gate · semantic ol/li + time[datetime] rail

Make it yours

  • Settle point: the entry 65% range end — lower values (40%) finish sooner; cover 50% makes items animate until mid-viewport.
  • Rise distance: the 24px in the keyframe; 12px whispers, 48px announces.
  • Marker pop: the 1.35 overshoot at 60% — set to 1 for a strict fade.
  • Stagger illusion: give even items animation-range: entry 0% entry 80% so columns settle at different scroll depths.

Gotchas — read before shipping

  • NEVER put opacity:0 on items outside the @supports gate — unsupported browsers would show a blank page forever. The gate must own both the hidden start state and the animation.
  • animation-range needs the same gate as animation-timeline — old Safari parses one but not the other and items freeze mid-entrance.
  • view() tracks the nearest SCROLLER: if a parent gains overflow:hidden for a border-radius clip, the timeline silently dies — use overflow:clip for clipping instead.
  • Keep entrance keyframes to transform/opacity only; animating height or margin in a scroll-driven animation causes layout thrash on every scroll frame.

Browser support

ChromeSafariFirefoxEdge
115+18.2+ (partial 17.4)128+ (flag; 2025 stable)115+

animation-timeline is Baseline newly-available 2025. The @supports gate means every other browser gets the identical timeline, statically rendered — nothing is lost.

Techniques used in this demo

Search CodeFronts

Loading…