Open demo

CSS code

HTML
<ol class="alt-tl">
  <li class="row"><span class="node" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 21h18M5 21V8l7-5 7 5v13M10 21v-6h4v6"/></svg></span>
    <div class="card"><time datetime="2014">2014</time><h2>Founded above a bakery</h2>
      <p>Two people, one plotter, and a lease measured in months.</p></div></li>

  <li class="row"><span class="node" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M13 3l-8 10h6l-2 8 8-10h-6z"/></svg></span>
    <div class="card"><time datetime="2017">2017</time><h2>First national campaign</h2>
      <p>A rebrand that ended up on four hundred billboards.</p></div></li>

  <li class="row"><span class="node" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg></span>
    <div class="card"><time datetime="2020">2020</time><h2>Remote, permanently</h2>
      <p>Twelve people across five time zones and no return-to-office plan.</p></div></li>

  <li class="row"><span class="node" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8.3-7 10-4-1.7-7-5.5-7-10V6z"/><path d="M9 12l2 2 4-4"/></svg></span>
    <div class="card"><time datetime="2023">2023</time><h2>Employee ownership</h2>
      <p>The studio bought itself, finalised in March.</p></div></li>
</ol>
CSS
/* Centred alternating timeline: an <ol> whose even items sit on the right of the line. One column below 760px. */
.alt-tl{--al-accent:#4f46e5;--al-line:#dde3f0;--al-border:#e3e8f2;--al-muted:#5f6a86;
  position:relative;width:100%;max-width:820px;margin:0;padding:8px 0;list-style:none}
.alt-tl,.alt-tl *{box-sizing:border-box}
.alt-tl :where(li,h2,p){margin:0;padding:0}
.alt-tl::before{content:"";position:absolute;left:50%;top:0;bottom:0;width:3px;margin-left:-1.5px;
  background:linear-gradient(180deg,var(--al-line),var(--al-accent),var(--al-line))}
.alt-tl .row{position:relative;width:50%;padding:0 40px 34px 0}
.alt-tl .row:nth-child(even){margin-left:50%;padding:0 0 34px 40px}
.alt-tl .node{position:absolute;top:2px;right:-23px;width:46px;height:46px;border-radius:14px;background:#fff;
  border:1px solid var(--al-border);display:grid;place-items:center;z-index:2;
  box-shadow:0 8px 18px -12px rgba(23,32,58,.5)}
.alt-tl .row:nth-child(even) .node{right:auto;left:-23px}
.alt-tl .node svg{display:block;width:21px;height:21px;stroke:var(--al-accent);fill:none;stroke-width:2}
.alt-tl .card{background:#fff;border:1px solid var(--al-border);border-radius:13px;padding:15px 17px;position:relative}
.alt-tl .card::after{content:"";position:absolute;top:16px;right:-7px;width:12px;height:12px;background:#fff;
  border-right:1px solid var(--al-border);border-top:1px solid var(--al-border);transform:rotate(45deg)}
.alt-tl .row:nth-child(even) .card::after{right:auto;left:-7px;transform:rotate(225deg)}
.alt-tl time{font-size:.73rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--al-accent)}
.alt-tl h2{font-size:.95rem;margin:5px 0;letter-spacing:-.01em}
.alt-tl p{font-size:.83rem;color:var(--al-muted);line-height:1.62}
@media(max-width:760px){
  .alt-tl::before{left:23px}
  .alt-tl .row,.alt-tl .row:nth-child(even){width:100%;margin-left:0;padding:0 0 28px 62px}
  .alt-tl .node,.alt-tl .row:nth-child(even) .node{left:0;right:auto}
  .alt-tl .card::after,.alt-tl .row:nth-child(even) .card::after{left:-7px;right:auto;transform:rotate(225deg)}
}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<ol class="alt-tl list-unstyled position-relative w-100 m-0">
  <li class="row-item position-relative"><span class="node d-grid bg-white" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 21h18M5 21V8l7-5 7 5v13M10 21v-6h4v6"/></svg></span>
    <div class="card-box bg-white position-relative"><time class="fw-bolder text-uppercase" datetime="2014">2014</time><h2>Founded above a bakery</h2>
      <p>Two people, one plotter, and a lease measured in months.</p></div></li>

  <li class="row-item position-relative"><span class="node d-grid bg-white" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M13 3l-8 10h6l-2 8 8-10h-6z"/></svg></span>
    <div class="card-box bg-white position-relative"><time class="fw-bolder text-uppercase" datetime="2017">2017</time><h2>First national campaign</h2>
      <p>A rebrand that ended up on four hundred billboards.</p></div></li>

  <li class="row-item position-relative"><span class="node d-grid bg-white" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg></span>
    <div class="card-box bg-white position-relative"><time class="fw-bolder text-uppercase" datetime="2020">2020</time><h2>Remote, permanently</h2>
      <p>Twelve people across five time zones and no return-to-office plan.</p></div></li>

  <li class="row-item position-relative"><span class="node d-grid bg-white" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8.3-7 10-4-1.7-7-5.5-7-10V6z"/><path d="M9 12l2 2 4-4"/></svg></span>
    <div class="card-box bg-white position-relative"><time class="fw-bolder text-uppercase" datetime="2023">2023</time><h2>Employee ownership</h2>
      <p>The studio bought itself, finalised in March.</p></div></li>
</ol>
CSS
/* Bootstrap list and utilities; the alternating placement and the single column below 760px are custom CSS.
   (.row-item rather than .row, so Bootstrap's grid .row does not apply.) */
.alt-tl{--al-accent:#4f46e5;--al-line:#dde3f0;--al-border:#e3e8f2;--al-muted:#5f6a86;max-width:820px;padding:8px 0}
.alt-tl::before{content:"";position:absolute;left:50%;top:0;bottom:0;width:3px;margin-left:-1.5px;
  background:linear-gradient(180deg,var(--al-line),var(--al-accent),var(--al-line))}
.alt-tl .row-item{width:50%;padding:0 40px 34px 0}
.alt-tl .row-item:nth-child(even){margin-left:50%;padding:0 0 34px 40px}
.alt-tl .node{position:absolute;top:2px;right:-23px;width:46px;height:46px;border-radius:14px;
  border:1px solid var(--al-border);place-items:center;z-index:2;box-shadow:0 8px 18px -12px rgba(23,32,58,.5)}
.alt-tl .row-item:nth-child(even) .node{right:auto;left:-23px}
.alt-tl .node svg{display:block;width:21px;height:21px;stroke:var(--al-accent);fill:none;stroke-width:2}
.alt-tl .card-box{border:1px solid var(--al-border);border-radius:13px;padding:15px 17px}
.alt-tl .card-box::after{content:"";position:absolute;top:16px;right:-7px;width:13px;height:13px;background:#fff;
  border-right:1px solid var(--al-border);border-top:1px solid var(--al-border);transform:rotate(45deg)}
.alt-tl .row-item:nth-child(even) .card-box::after{right:auto;left:-7px;transform:rotate(225deg)}
.alt-tl time{font-size:.73rem;letter-spacing:.06em;color:var(--al-accent)}
.alt-tl h2{font-size:.95rem;font-weight:700;line-height:normal;margin:5px 0;letter-spacing:-.01em}
.alt-tl p{font-size:.83rem;color:var(--al-muted);line-height:1.62;margin:0}
@media(max-width:760px){
  .alt-tl::before{left:23px}
  .alt-tl .row-item,.alt-tl .row-item:nth-child(even){width:100%;margin-left:0;padding:0 0 28px 62px}
  .alt-tl .node,.alt-tl .row-item:nth-child(even) .node{left:0;right:auto}
  .alt-tl .card-box::after,.alt-tl .row-item:nth-child(even) .card-box::after{left:-7px;right:auto;transform:rotate(225deg)}
}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<ol class="relative w-full max-w-[820px] py-2 before:absolute before:top-0 before:bottom-0 before:left-1/2 before:-ml-[1.5px] before:w-[3px] before:bg-[linear-gradient(180deg,var(--color-al-line),var(--color-al-accent),var(--color-al-line))] max-[760px]:before:left-[23px]">
  <li class="group relative w-1/2 pr-10 pb-[34px] even:ml-[50%] even:pr-0 even:pl-10 max-[760px]:w-full max-[760px]:ml-0 max-[760px]:pr-0 max-[760px]:pl-[62px] max-[760px]:pb-7 max-[760px]:even:ml-0 max-[760px]:even:pl-[62px]"><span class="absolute top-0.5 -right-[23px] z-[2] grid size-[46px] place-items-center rounded-[14px] border border-al-border bg-white shadow-[0_8px_18px_-12px_rgba(23,32,58,.5)] group-even:right-auto group-even:-left-[23px] max-[760px]:right-auto max-[760px]:left-0 max-[760px]:group-even:left-0" aria-hidden="true"><svg class="size-[21px] fill-none stroke-al-accent stroke-2" viewBox="0 0 24 24"><path d="M3 21h18M5 21V8l7-5 7 5v13M10 21v-6h4v6"/></svg></span>
    <div class="relative rounded-[13px] border border-al-border bg-white px-[17px] py-[15px] after:absolute after:top-4 after:-right-[7px] after:size-[13px] after:rotate-45 after:border-t after:border-r after:border-al-border after:bg-white group-even:after:right-auto group-even:after:-left-[7px] group-even:after:rotate-[225deg] max-[760px]:after:right-auto max-[760px]:after:-left-[7px] max-[760px]:after:rotate-[225deg]"><time class="text-[.73rem] font-extrabold tracking-[.06em] text-al-accent uppercase" datetime="2014">2014</time><h2 class="my-[5px] text-[.95rem] font-bold tracking-[-.01em]">Founded above a bakery</h2>
      <p class="text-[.83rem] leading-[1.62] text-al-muted">Two people, one plotter, and a lease measured in months.</p></div></li>

  <li class="group relative w-1/2 pr-10 pb-[34px] even:ml-[50%] even:pr-0 even:pl-10 max-[760px]:w-full max-[760px]:ml-0 max-[760px]:pr-0 max-[760px]:pl-[62px] max-[760px]:pb-7 max-[760px]:even:ml-0 max-[760px]:even:pl-[62px]"><span class="absolute top-0.5 -right-[23px] z-[2] grid size-[46px] place-items-center rounded-[14px] border border-al-border bg-white shadow-[0_8px_18px_-12px_rgba(23,32,58,.5)] group-even:right-auto group-even:-left-[23px] max-[760px]:right-auto max-[760px]:left-0 max-[760px]:group-even:left-0" aria-hidden="true"><svg class="size-[21px] fill-none stroke-al-accent stroke-2" viewBox="0 0 24 24"><path d="M13 3l-8 10h6l-2 8 8-10h-6z"/></svg></span>
    <div class="relative rounded-[13px] border border-al-border bg-white px-[17px] py-[15px] after:absolute after:top-4 after:-right-[7px] after:size-[13px] after:rotate-45 after:border-t after:border-r after:border-al-border after:bg-white group-even:after:right-auto group-even:after:-left-[7px] group-even:after:rotate-[225deg] max-[760px]:after:right-auto max-[760px]:after:-left-[7px] max-[760px]:after:rotate-[225deg]"><time class="text-[.73rem] font-extrabold tracking-[.06em] text-al-accent uppercase" datetime="2017">2017</time><h2 class="my-[5px] text-[.95rem] font-bold tracking-[-.01em]">First national campaign</h2>
      <p class="text-[.83rem] leading-[1.62] text-al-muted">A rebrand that ended up on four hundred billboards.</p></div></li>

  <li class="group relative w-1/2 pr-10 pb-[34px] even:ml-[50%] even:pr-0 even:pl-10 max-[760px]:w-full max-[760px]:ml-0 max-[760px]:pr-0 max-[760px]:pl-[62px] max-[760px]:pb-7 max-[760px]:even:ml-0 max-[760px]:even:pl-[62px]"><span class="absolute top-0.5 -right-[23px] z-[2] grid size-[46px] place-items-center rounded-[14px] border border-al-border bg-white shadow-[0_8px_18px_-12px_rgba(23,32,58,.5)] group-even:right-auto group-even:-left-[23px] max-[760px]:right-auto max-[760px]:left-0 max-[760px]:group-even:left-0" aria-hidden="true"><svg class="size-[21px] fill-none stroke-al-accent stroke-2" viewBox="0 0 24 24"><circle cx="12" cy="8" r="3.4"/><path d="M5 20c0-3.4 3-5.6 7-5.6s7 2.2 7 5.6"/></svg></span>
    <div class="relative rounded-[13px] border border-al-border bg-white px-[17px] py-[15px] after:absolute after:top-4 after:-right-[7px] after:size-[13px] after:rotate-45 after:border-t after:border-r after:border-al-border after:bg-white group-even:after:right-auto group-even:after:-left-[7px] group-even:after:rotate-[225deg] max-[760px]:after:right-auto max-[760px]:after:-left-[7px] max-[760px]:after:rotate-[225deg]"><time class="text-[.73rem] font-extrabold tracking-[.06em] text-al-accent uppercase" datetime="2020">2020</time><h2 class="my-[5px] text-[.95rem] font-bold tracking-[-.01em]">Remote, permanently</h2>
      <p class="text-[.83rem] leading-[1.62] text-al-muted">Twelve people across five time zones and no return-to-office plan.</p></div></li>

  <li class="group relative w-1/2 pr-10 pb-[34px] even:ml-[50%] even:pr-0 even:pl-10 max-[760px]:w-full max-[760px]:ml-0 max-[760px]:pr-0 max-[760px]:pl-[62px] max-[760px]:pb-7 max-[760px]:even:ml-0 max-[760px]:even:pl-[62px]"><span class="absolute top-0.5 -right-[23px] z-[2] grid size-[46px] place-items-center rounded-[14px] border border-al-border bg-white shadow-[0_8px_18px_-12px_rgba(23,32,58,.5)] group-even:right-auto group-even:-left-[23px] max-[760px]:right-auto max-[760px]:left-0 max-[760px]:group-even:left-0" aria-hidden="true"><svg class="size-[21px] fill-none stroke-al-accent stroke-2" viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8.3-7 10-4-1.7-7-5.5-7-10V6z"/><path d="M9 12l2 2 4-4"/></svg></span>
    <div class="relative rounded-[13px] border border-al-border bg-white px-[17px] py-[15px] after:absolute after:top-4 after:-right-[7px] after:size-[13px] after:rotate-45 after:border-t after:border-r after:border-al-border after:bg-white group-even:after:right-auto group-even:after:-left-[7px] group-even:after:rotate-[225deg] max-[760px]:after:right-auto max-[760px]:after:-left-[7px] max-[760px]:after:rotate-[225deg]"><time class="text-[.73rem] font-extrabold tracking-[.06em] text-al-accent uppercase" datetime="2023">2023</time><h2 class="my-[5px] text-[.95rem] font-bold tracking-[-.01em]">Employee ownership</h2>
      <p class="text-[.83rem] leading-[1.62] text-al-muted">The studio bought itself, finalised in March.</p></div></li>
</ol>
CSS
@theme {
  --color-al-accent: #4f46e5;
  --color-al-line: #dde3f0;
  --color-al-border: #e3e8f2;
  --color-al-muted: #5f6a86;
}

About this set

A centred vertical timeline with cards alternating left and right of a gradient line and a rounded icon tile on the line for each milestone. Every card has a small pointer facing the line, an uppercase year, a heading and a sentence. The demo tells a studio’s story in four milestones, from being founded above a bakery to employee ownership, with a building, a lightning bolt, a person and a shield as icons. Use it for an about page, a company history or a product’s major releases where a few milestones need room to breathe. The milestones are an ordered list, and the alternation comes from :nth-child(even), which moves every second item to the right half and flips its icon and pointer, so the markup stays a plain sequence. Below 760 pixels the line moves to the left edge, all cards line up to its right and every pointer faces left, keeping a single readable column on a phone. The years are time elements and the icons are hidden from screen readers. The Bootstrap version uses a .row-item class so Bootstrap’s grid .row is not triggered, and the Tailwind version uses even: and group-even: variants.

What’s included

  • Cards alternate sides of a centred gradient line
  • Icon tile on the line for each milestone
  • Card pointers that face the line
  • Single left-aligned column below 760px
  • Alternation from :nth-child(even), no extra classes

Accessibility

Milestones are an ordered list in date order with headings and years in time elements, so the reading order is the chronological order on every screen size. The icon tiles are aria-hidden, and the line and pointers are CSS decoration. Body text was darkened to meet 4.5:1, and nothing animates.

Customise it

Change --al-accent, --al-line, --al-border and --al-muted on .alt-tl (plain CSS and Bootstrap), or the --color-al-* theme colours in Tailwind. The 760px media query, or the max-[760px]: prefix, sets when it becomes one column.