32 CSS Timelines16 / 32

Pure CSSMIT licensed

Accordion Timeline — Expandable Milestones with Exclusive Open

The space-saver: a vertical milestone rail where each date row is a <details> disclosure — one click expands the story, and the name attribute makes the set EXCLUSIVE (opening one closes the others) with zero JavaScript — the mobile-first pattern for company histories, FAQs-over-time and product changelogs.

Published Updated

Live Demo
Try it

The code

<section class="tls-16" aria-label="Accordion timeline demo">
  <div class="tls-16__wrap">
    <h3 class="tls-16__h">Twelve years, four turns</h3>
    <ol class="tls-16__list" role="list">
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2014">2014</time><span class="tls-16__ti">Founded in a garage that was actually a garage</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Two founders, one contract, a space heater. The first product was a spreadsheet plugin nobody remembers — but the customer list from it became everything.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16" open>
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2019">2019</time><span class="tls-16__ti">The pivot: we deleted 80% of the product</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Usage data said one feature carried the company. We cut the rest in a single release, halved onboarding time, and revenue doubled inside a year. The hardest board meeting we ever had.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2023">2023</time><span class="tls-16__ti">First profitable year · team of 40</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Profitability changed the conversations: no runway math, just product. We opened the Toronto office and shipped the platform rewrite.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2026">2026</time><span class="tls-16__ti">10,000 teams · the second act begins</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>The roadmap for the next decade is the one we wrote in 2014 and couldn't afford. Now we can.</p></div></div>
        </details>
      </li>
    </ol>
  </div>
</section>
.tls-16,
.tls-16 *,
.tls-16 *::before,
.tls-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-16 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #9333ea;
  --tl-line: #e5e0eb;
  --tl-ink: #241b2e;
  --tl-mut: #8a7f96;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #faf8fc;
  padding: 56px 24px;
  display: grid;
  place-items: center;
}

.tls-16__wrap {
  width: min(600px,100%);
}

.tls-16__h {
  font-size: clamp(20px,3.2vw,26px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 22px;
}

.tls-16__list {
  list-style: none;
}

.tls-16__item {
  position: relative;
  padding-left: 30px;
}

.tls-16__item::before {
  content: '';
  position: absolute;
  left: 7px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--tl-line);
}

.tls-16__item:first-child::before {
  top: 24px;
}

.tls-16__item:last-child::before {
  bottom: calc(100% - 24px);
}

.tls-16__item::after {
  content: '';
  position: absolute;
  left: 2px;
  top: 18px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--tl-mut);
  box-shadow: 0 0 0 4px #faf8fc;
  transition: border-color .2s,scale .2s;
}

.tls-16__item:has(.tls-16__d[open])::after {
  border-color: var(--tl-accent);
  scale: 1.25;
}

.tls-16__sum {
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 14px;
  cursor: pointer;
  padding: 14px 6px;
  user-select: none;
  border-radius: 10px;
  transition: background .15s;
}

.tls-16__sum::-webkit-details-marker {
  display: none;
}

.tls-16__sum:hover {
  background: #f3eefa;
}

.tls-16__d[open] .tls-16__sum .tls-16__ti {
  color: var(--tl-accent);
}

.tls-16__yr {
  flex: none;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--tl-mut);
}

.tls-16__ti {
  flex: 1;
  font-size: 15.5px;
  font-weight: 700;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  transition: color .2s;
  text-wrap: pretty;
}

.tls-16__pm {
  flex: none;
  position: relative;
  width: 14px;
  height: 14px;
  align-self: center;
}

.tls-16__pm::before,
.tls-16__pm::after {
  content: '';
  position: absolute;
  background: var(--tl-mut);
  border-radius: 1px;
  transition: transform .25s,background .2s;
}

.tls-16__pm::before {
  left: 0;
  right: 0;
  top: 6px;
  height: 2px;
}

.tls-16__pm::after {
  top: 0;
  bottom: 0;
  left: 6px;
  width: 2px;
}

.tls-16__d[open] .tls-16__pm::after {
  transform: rotate(90deg);
}

.tls-16__d[open] .tls-16__pm::before,
.tls-16__d[open] .tls-16__pm::after {
  background: var(--tl-accent);
}

.tls-16__reveal {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .35s ease;
}

.tls-16__d[open] .tls-16__reveal {
  grid-template-rows: 1fr;
}

.tls-16__panel {
  min-height: 0;
  overflow: hidden;
}

.tls-16__panel p {
  font-size: 14px;
  line-height: 1.65;
  color: #4a4055;
  padding: 2px 6px 18px calc(13px + 14px + 13ch - 13ch);
  max-width: 56ch;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .tls-16__reveal {
    transition: none;
  }

  .tls-16__pm::before,
    .tls-16__pm::after {
    transition: none;
  }
}
/* No JavaScript — details name="tls-16" gives native EXCLUSIVE accordion behavior (Baseline widely-available); the reveal animates grid-template-rows 0fr→1fr. */
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: Accordion Timeline — Expandable Milestones with Exclusive Open
Source: https://codefronts.com/layouts/css-timelines/accordion-timeline/

The space-saver: a vertical milestone rail where each date row is a <details> disclosure — one click expands the story, and the name attribute makes the set EXCLUSIVE (opening one closes the others) with zero JavaScript — the mobile-first pattern for company histories, FAQs-over-time and product changelogs.
## HTML
```html
<section class="tls-16" aria-label="Accordion timeline demo">
  <div class="tls-16__wrap">
    <h3 class="tls-16__h">Twelve years, four turns</h3>
    <ol class="tls-16__list" role="list">
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2014">2014</time><span class="tls-16__ti">Founded in a garage that was actually a garage</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Two founders, one contract, a space heater. The first product was a spreadsheet plugin nobody remembers — but the customer list from it became everything.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16" open>
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2019">2019</time><span class="tls-16__ti">The pivot: we deleted 80% of the product</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Usage data said one feature carried the company. We cut the rest in a single release, halved onboarding time, and revenue doubled inside a year. The hardest board meeting we ever had.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2023">2023</time><span class="tls-16__ti">First profitable year · team of 40</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>Profitability changed the conversations: no runway math, just product. We opened the Toronto office and shipped the platform rewrite.</p></div></div>
        </details>
      </li>
      <li class="tls-16__item">
        <details class="tls-16__d" name="tls-16">
          <summary class="tls-16__sum"><time class="tls-16__yr" datetime="2026">2026</time><span class="tls-16__ti">10,000 teams · the second act begins</span><span class="tls-16__pm" aria-hidden="true"></span></summary>
          <div class="tls-16__reveal"><div class="tls-16__panel"><p>The roadmap for the next decade is the one we wrote in 2014 and couldn't afford. Now we can.</p></div></div>
        </details>
      </li>
    </ol>
  </div>
</section>
```
## CSS
```css
.tls-16,
.tls-16 *,
.tls-16 *::before,
.tls-16 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.tls-16 {
  min-height: 100vh;
  width: 100%;
  --tl-accent: #9333ea;
  --tl-line: #e5e0eb;
  --tl-ink: #241b2e;
  --tl-mut: #8a7f96;
  font-family: 'Segoe UI',system-ui,sans-serif;
  background: #faf8fc;
  padding: 56px 24px;
  display: grid;
  place-items: center;
}

.tls-16__wrap {
  width: min(600px,100%);
}

.tls-16__h {
  font-size: clamp(20px,3.2vw,26px);
  font-weight: 800;
  color: var(--tl-ink);
  letter-spacing: -.02em;
  margin-bottom: 22px;
}

.tls-16__list {
  list-style: none;
}

.tls-16__item {
  position: relative;
  padding-left: 30px;
}

.tls-16__item::before {
  content: '';
  position: absolute;
  left: 7px;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--tl-line);
}

.tls-16__item:first-child::before {
  top: 24px;
}

.tls-16__item:last-child::before {
  bottom: calc(100% - 24px);
}

.tls-16__item::after {
  content: '';
  position: absolute;
  left: 2px;
  top: 18px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--tl-mut);
  box-shadow: 0 0 0 4px #faf8fc;
  transition: border-color .2s,scale .2s;
}

.tls-16__item:has(.tls-16__d[open])::after {
  border-color: var(--tl-accent);
  scale: 1.25;
}

.tls-16__sum {
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 14px;
  cursor: pointer;
  padding: 14px 6px;
  user-select: none;
  border-radius: 10px;
  transition: background .15s;
}

.tls-16__sum::-webkit-details-marker {
  display: none;
}

.tls-16__sum:hover {
  background: #f3eefa;
}

.tls-16__d[open] .tls-16__sum .tls-16__ti {
  color: var(--tl-accent);
}

.tls-16__yr {
  flex: none;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 13px;
  font-weight: 700;
  color: var(--tl-mut);
}

.tls-16__ti {
  flex: 1;
  font-size: 15.5px;
  font-weight: 700;
  color: var(--tl-ink);
  letter-spacing: -.01em;
  transition: color .2s;
  text-wrap: pretty;
}

.tls-16__pm {
  flex: none;
  position: relative;
  width: 14px;
  height: 14px;
  align-self: center;
}

.tls-16__pm::before,
.tls-16__pm::after {
  content: '';
  position: absolute;
  background: var(--tl-mut);
  border-radius: 1px;
  transition: transform .25s,background .2s;
}

.tls-16__pm::before {
  left: 0;
  right: 0;
  top: 6px;
  height: 2px;
}

.tls-16__pm::after {
  top: 0;
  bottom: 0;
  left: 6px;
  width: 2px;
}

.tls-16__d[open] .tls-16__pm::after {
  transform: rotate(90deg);
}

.tls-16__d[open] .tls-16__pm::before,
.tls-16__d[open] .tls-16__pm::after {
  background: var(--tl-accent);
}

.tls-16__reveal {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .35s ease;
}

.tls-16__d[open] .tls-16__reveal {
  grid-template-rows: 1fr;
}

.tls-16__panel {
  min-height: 0;
  overflow: hidden;
}

.tls-16__panel p {
  font-size: 14px;
  line-height: 1.65;
  color: #4a4055;
  padding: 2px 6px 18px calc(13px + 14px + 13ch - 13ch);
  max-width: 56ch;
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .tls-16__reveal {
    transition: none;
  }

  .tls-16__pm::before,
    .tls-16__pm::after {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — details name="tls-16" gives native EXCLUSIVE accordion behavior (Baseline widely-available); the reveal animates grid-template-rows 0fr→1fr. */
```

How this works

The platform primitive this demo teaches: <details name="tls-16">. Give every milestone's details element the same name and the browser enforces accordion exclusivity natively — open one, the others close, with correct ARIA states, keyboard toggling and no state management. This is a Baseline widely-available feature and most galleries still haven't caught up.

Each <summary> is the milestone row itself: year, title, and a plus-morphing-to-minus marker drawn from two 2px bars (the vertical bar rotates 90° when open — a transform, so it animates on the compositor). The reveal uses the grid-rows 0fr→1fr wrapper pattern for a smooth open even though details itself can't transition height.

The rail and dots are the standard per-item pseudo-element spine, but the open item's dot swells and tints — details[open] is a styling hook for the WHOLE item, which is what makes details-based timelines so much cleaner than checkbox hacks: state lives in the element that owns it.

Techniques used: details name="…" native exclusive accordion (Baseline widely-available) · summary-as-milestone-row with CSS-drawn plus/minus morph · grid-template-rows 0fr→1fr smooth reveal · details[open] styling hook for dot/row emphasis · per-item rail spine · time[datetime] date column

Make it yours

  • Non-exclusive mode: delete the name attribute — each milestone then toggles independently.
  • Start state: add open to whichever details should greet the user expanded (as shipped: the 2019 entry).
  • Marker style: the plus is two bars; swap for a chevron by rotating one bar ±45° in the open state.
  • Reveal speed: the .35s grid-rows transition; long content reads better at .4–.5s.

Gotchas — read before shipping

  • The name attribute is the exclusivity — if entries wrap in different parents that's fine, but the names must match EXACTLY (they're grouped document-wide).
  • Never put interactive elements (links, buttons) inside summary — clicks toggle the accordion instead of following the link; put actions in the revealed panel.
  • details name= shipped in 2024 — older browsers just behave non-exclusively; acceptable degradation, don't polyfill.
  • Keep summary content phrasing-level; block elements inside summary are technically invalid HTML and screen readers stumble on them.

Browser support

ChromeSafariFirefoxEdge
120+17.2+130+120+

Exclusive accordion via details[name] is baseline since 2024; without it entries open independently — everything else is universal.

Techniques used in this demo

Search CodeFronts

Loading…