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

Metro Map Timeline — Transit Lines with Interchange Stations

A career or product history drawn as a subway map: two colored lines running in parallel, stations as ring dots with flag labels, and an interchange station where the lines meet — the visual grammar everyone on Earth already knows how to read, built from borders and radial dots with zero SVG.

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-30" aria-label="Metro map timeline demo">
  <div class="tls-30__inner">
    <h3 class="tls-30__title">Ten years, two tracks</h3>
    <div class="tls-30__legend"><span class="tls-30__key" style="--k:#dc2626">Engineering line</span><span class="tls-30__key" style="--k:#2563eb">Design line</span></div>
    <div class="tls-30__map">
      <ol class="tls-30__line" style="--l:#dc2626" role="list" aria-label="Engineering line">
        <li class="tls-30__st" style="--x:4%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2016">2016</time>Junior dev</p></li>
        <li class="tls-30__st" style="--x:30%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2018">2018</time>Senior engineer</p></li>
        <li class="tls-30__st" style="--x:56%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2020">2020</time>Tech lead</p></li>
        <li class="tls-30__st tls-30__st--xchg" style="--x:82%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2023">2023</time><b>Design systems lead</b></p></li>
      </ol>
      <ol class="tls-30__line tls-30__line--2" style="--l:#2563eb" role="list" aria-label="Design line">
        <li class="tls-30__st" style="--x:17%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2017">2017</time>Night-school Figma</p></li>
        <li class="tls-30__st" style="--x:43%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2019">2019</time>First shipped UI</p></li>
        <li class="tls-30__st" style="--x:69%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2022">2022</time>Token architecture</p></li>
        <li class="tls-30__st tls-30__st--xchg" style="--x:82%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag tls-30__flag--hidden"><time datetime="2023">2023</time>Interchange</p></li>
      </ol>
    </div>
  </div>
</section>
.tls-30,
.tls-30 *,
.tls-30 *::before,
.tls-30 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-30 {
  min-height: 100vh;
  width: 100%;
  --tl-ink: #1c1917;
  --tl-mut: #78716c;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #fbfaf8;
  padding: 64px 24px;
  display: grid;
  place-items: center;
}

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

.tls-30__title {
  font-size: clamp(22px,3.5vw,28px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 14px;
}

.tls-30__legend {
  display: flex;
  gap: 18px;
  margin-bottom: 44px;
}

.tls-30__key {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--tl-mut);
}

.tls-30__key::before {
  content: '';
  width: 22px;
  height: 6px;
  border-radius: 3px;
  background: var(--k);
}

.tls-30__map {
  position: relative;
  padding: 30px 0 20px;
}

.tls-30__line {
  list-style: none;
  position: relative;
  height: 110px;
  border-top: 7px solid var(--l);
  border-radius: 4px;
  margin: 0 12px;
}

.tls-30__line--2 {
  margin-top: -48px;
}

.tls-30__st {
  position: absolute;
  left: var(--x);
  top: 0;
}

.tls-30__dot {
  position: absolute;
  top: -14px;
  left: -10px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 5px solid var(--l);
  box-shadow: 0 0 0 3px #fff;
}

.tls-30__st--xchg .tls-30__dot {
  top: -17px;
  left: -13px;
  width: 26px;
  height: 26px;
  border-width: 6px;
  border-color: var(--tl-ink);
  z-index: 2;
}

.tls-30__flag {
  position: absolute;
  top: 16px;
  left: -8px;
  width: 120px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--tl-mut);
}

.tls-30__st:nth-child(even) .tls-30__flag {
  top: auto;
  bottom: 26px;
}

.tls-30__flag time {
  display: block;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  color: var(--tl-ink);
}

.tls-30__flag b {
  color: var(--tl-ink);
}

.tls-30__flag--hidden {
  visibility: hidden;
}

@media (max-width: 600px) {
  .tls-30__map {
    padding: 0;
  }

  .tls-30__line {
    height: auto;
    border-top: none;
    border-left: 7px solid var(--l);
    margin: 0 0 30px 12px;
    padding: 6px 0 6px 26px;
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .tls-30__line--2 {
    margin-top: 0;
  }

  .tls-30__st {
    position: static;
  }

  .tls-30__dot {
    top: auto;
    left: -42px;
    margin-top: 2px;
    position: relative;
    display: inline-block;
  }

  .tls-30__flag {
    position: static;
    width: auto;
    margin-top: -22px;
    padding-left: 0;
  }

  .tls-30__st:nth-child(even) .tls-30__flag {
    position: static;
  }

  .tls-30__flag--hidden {
    visibility: visible;
  }
}
/* No JavaScript — rails are thick borders, stations are ring dots positioned by a --x token per stop, and the interchange is one shared station drawn larger in ink. */
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: Metro Map Timeline — Transit Lines with Interchange Stations
Source: https://codefronts.com/layouts/css-timelines/railroad-stations/

A career or product history drawn as a subway map: two colored lines running in parallel, stations as ring dots with flag labels, and an interchange station where the lines meet — the visual grammar everyone on Earth already knows how to read, built from borders and radial dots with zero SVG.
## HTML
```html
<section class="tls-30" aria-label="Metro map timeline demo">
  <div class="tls-30__inner">
    <h3 class="tls-30__title">Ten years, two tracks</h3>
    <div class="tls-30__legend"><span class="tls-30__key" style="--k:#dc2626">Engineering line</span><span class="tls-30__key" style="--k:#2563eb">Design line</span></div>
    <div class="tls-30__map">
      <ol class="tls-30__line" style="--l:#dc2626" role="list" aria-label="Engineering line">
        <li class="tls-30__st" style="--x:4%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2016">2016</time>Junior dev</p></li>
        <li class="tls-30__st" style="--x:30%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2018">2018</time>Senior engineer</p></li>
        <li class="tls-30__st" style="--x:56%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2020">2020</time>Tech lead</p></li>
        <li class="tls-30__st tls-30__st--xchg" style="--x:82%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2023">2023</time><b>Design systems lead</b></p></li>
      </ol>
      <ol class="tls-30__line tls-30__line--2" style="--l:#2563eb" role="list" aria-label="Design line">
        <li class="tls-30__st" style="--x:17%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2017">2017</time>Night-school Figma</p></li>
        <li class="tls-30__st" style="--x:43%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2019">2019</time>First shipped UI</p></li>
        <li class="tls-30__st" style="--x:69%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag"><time datetime="2022">2022</time>Token architecture</p></li>
        <li class="tls-30__st tls-30__st--xchg" style="--x:82%"><span class="tls-30__dot" aria-hidden="true"></span><p class="tls-30__flag tls-30__flag--hidden"><time datetime="2023">2023</time>Interchange</p></li>
      </ol>
    </div>
  </div>
</section>
```
## CSS
```css
.tls-30,
.tls-30 *,
.tls-30 *::before,
.tls-30 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-30 {
  min-height: 100vh;
  width: 100%;
  --tl-ink: #1c1917;
  --tl-mut: #78716c;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #fbfaf8;
  padding: 64px 24px;
  display: grid;
  place-items: center;
}

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

.tls-30__title {
  font-size: clamp(22px,3.5vw,28px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 14px;
}

.tls-30__legend {
  display: flex;
  gap: 18px;
  margin-bottom: 44px;
}

.tls-30__key {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--tl-mut);
}

.tls-30__key::before {
  content: '';
  width: 22px;
  height: 6px;
  border-radius: 3px;
  background: var(--k);
}

.tls-30__map {
  position: relative;
  padding: 30px 0 20px;
}

.tls-30__line {
  list-style: none;
  position: relative;
  height: 110px;
  border-top: 7px solid var(--l);
  border-radius: 4px;
  margin: 0 12px;
}

.tls-30__line--2 {
  margin-top: -48px;
}

.tls-30__st {
  position: absolute;
  left: var(--x);
  top: 0;
}

.tls-30__dot {
  position: absolute;
  top: -14px;
  left: -10px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  border: 5px solid var(--l);
  box-shadow: 0 0 0 3px #fff;
}

.tls-30__st--xchg .tls-30__dot {
  top: -17px;
  left: -13px;
  width: 26px;
  height: 26px;
  border-width: 6px;
  border-color: var(--tl-ink);
  z-index: 2;
}

.tls-30__flag {
  position: absolute;
  top: 16px;
  left: -8px;
  width: 120px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--tl-mut);
}

.tls-30__st:nth-child(even) .tls-30__flag {
  top: auto;
  bottom: 26px;
}

.tls-30__flag time {
  display: block;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  color: var(--tl-ink);
}

.tls-30__flag b {
  color: var(--tl-ink);
}

.tls-30__flag--hidden {
  visibility: hidden;
}

@media (max-width: 600px) {
  .tls-30__map {
    padding: 0;
  }

  .tls-30__line {
    height: auto;
    border-top: none;
    border-left: 7px solid var(--l);
    margin: 0 0 30px 12px;
    padding: 6px 0 6px 26px;
    display: flex;
    flex-direction: column;
    gap: 20px;
  }

  .tls-30__line--2 {
    margin-top: 0;
  }

  .tls-30__st {
    position: static;
  }

  .tls-30__dot {
    top: auto;
    left: -42px;
    margin-top: 2px;
    position: relative;
    display: inline-block;
  }

  .tls-30__flag {
    position: static;
    width: auto;
    margin-top: -22px;
    padding-left: 0;
  }

  .tls-30__st:nth-child(even) .tls-30__flag {
    position: static;
  }

  .tls-30__flag--hidden {
    visibility: visible;
  }
}
```

## JavaScript
```js
/* No JavaScript — rails are thick borders, stations are ring dots positioned by a --x token per stop, and the interchange is one shared station drawn larger in ink. */
```

How this works

Each line is a horizontal flex rail: a thick rounded border-top in the line's color, with stations as absolutely-positioned ring dots along it (white core, 4px colored ring — the international 'station' mark). Station labels alternate above and below the rail via nth-child, angled 0° here but one rotate from the diagonal London-style flags.

The interchange is the storytelling move: both rails pass through one shared station — a larger double-ring dot positioned at the same x-offset on both lines, visually fusing them where the two tracks converged (the demo: an engineering line and a design line meeting at 'Design systems lead'). The convergence is drawn by giving the second line a translateY that closes the gap at that station's column.

Under the map, a legend names each line with a color swatch AND a text label; every station is also a real <li> in source order per line, so the document reads as two clean lists even though the visual is a map. Reduced-motion isn't needed — nothing moves — but a media query re-stacks the two lines vertically with labels all-below when horizontal room runs out.

Techniques used: border-drawn transit rails with ring-dot stations (zero SVG) · alternating above/below flag labels via nth-child · shared interchange station fusing two rails · color + text legend (line identity never color-only) · per-line ol/li source semantics · responsive re-stack for narrow containers

Make it yours

  • Line colors: two tokens (--l1/--l2); official transit palettes (Central red, Victoria blue) read instantly.
  • Stations: each is one li — position rides a --x percentage custom property per station.
  • Diagonal flags: add rotate:-40deg + transform-origin:left bottom to the labels for the classic look.
  • Third line: duplicate a rail with a new token and offset; interchanges are just shared --x values.

Gotchas — read before shipping

  • Keep station labels SHORT (2–4 words) — metro maps work because stations are names, not sentences; long labels collide at adjacent stations.
  • The ring dots must sit exactly on the rail: their top offset is calc(ring-height/2) above the border — an off-by-2px here makes every station look derailed.
  • Two lines crossing WITHOUT an interchange dot implies they don't connect — in transit grammar that's meaningful; only fuse rails where the story actually connects.
  • Don't skip the legend: the lines' meaning (e.g. 'Engineering track' vs 'Design track') must be stated in text, not inferred from color.

Browser support

ChromeSafariFirefoxEdge
allallallall

Borders, absolute positioning and custom properties — the entire map is baseline CSS from a decade ago; it even prints correctly.

Techniques used in this demo

Search CodeFronts

Loading…