Home / Snippets / UI Components /
Timeline
Vertical timeline with connected dots and alternating content cards. Pure CSS using border and pseudo-elements.
-
March 2026
Project kickoff
Initial design approved. Repo created and team onboarded to new workflow.
-
April 2026
Alpha release
Core features shipped. Internal testing revealed three critical layout bugs — all fixed within 48 hours.
-
May 2026
Public beta
Opened to 500 early adopters. Feedback collected via in-app survey.
-
June 2026
v1.0 launch
Full public launch with documentation site and onboarding flow.
Quick implementation
/* HTML:
<ul class="tl">
<li class="tl-item">
<p class="tl-date">March 2026</p>
<div class="tl-card">
<h3>Event title</h3>
<p>Event description.</p>
</div>
</li>
</ul>
*/
.tl {
position: relative;
padding-left: 2rem;
list-style: none;
margin: 0;
}
/* The vertical connecting line */
.tl::before {
content: '';
position: absolute;
left: 0.5625rem;
top: 0.5rem;
bottom: 0.5rem;
width: 2px;
background: oklch(0.35 0.05 265);
}
.tl-item {
position: relative;
padding-bottom: 1.75rem;
}
.tl-item:last-child { padding-bottom: 0; }
/* The dot for each event */
.tl-item::before {
content: '';
position: absolute;
left: -1.625rem;
top: 0.35rem;
width: 0.75rem;
height: 0.75rem;
border-radius: 50%;
background: oklch(0.52 0.22 265);
border: 2px solid oklch(0.13 0.02 260);
box-shadow: 0 0 0 2px oklch(0.52 0.22 265 / 0.4);
}
.tl-date {
font-size: 0.75rem;
font-weight: 600;
color: oklch(0.72 0.19 265);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.25rem;
}
.tl-card {
background: oklch(0.19 0.02 260);
border: 1px solid oklch(0.28 0.03 260);
border-radius: 0.5rem;
padding: 0.875rem 1rem;
}
.tl-card h3 {
font-size: 0.95rem;
font-weight: 700;
margin: 0 0 0.25rem;
}
.tl-card p {
font-size: 0.85rem;
color: oklch(0.63 0.02 260);
margin: 0;
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building accessible layout components.
Goal: A vertical timeline component with a continuous connecting line, dot markers per event, date labels, and card-style content — pure CSS, no JavaScript.
Technical constraints:
- The connecting line is drawn with a ::before pseudo-element on the list container, absolutely positioned between the first and last dot.
- Each item's dot is its own ::before, sized, border-radius: 50%, with a double-ring glow via box-shadow.
- Use padding-left on the list and negative left offsets on pseudo-elements to align dots on the line precisely.
- Card backgrounds use oklch() at low lightness (around 0.19) with a border 1 step lighter.
- Date labels use uppercase letter-spacing and a distinct accent hue.
- All colors use oklch() — no hex, no hsl().
Framework variant (pick one):
A) Vanilla HTML + CSS — semantic <ul>/<li> list with BEM modifier classes.
B) React component — accept an array of event objects (date, title, description) and render the timeline dynamically.
Edge cases to handle:
- Single event: the vertical line still renders cleanly with bottom: 0.5rem stopping it short.
- Very long descriptions: card must expand vertically without overflowing the dot.
- Right-to-left layouts: mirror the left offsets using logical properties (padding-inline-start, inset-inline-start).
- Reduced motion: if the timeline animates items in on scroll, wrap in @media (prefers-reduced-motion: reduce).
Return HTML + CSS.
Why this matters in 2026
Timelines communicate sequence and progress — project histories, changelogs, event feeds, user activity streams. The temptation is to reach for a charting library, but the entire visual structure is achievable with a <ul>, two ::before pseudo-elements, and absolute positioning. No SVG, no canvas, no JavaScript. That means it's server-renderable, accessible by default, and adds zero runtime to your page.
The logic
The vertical line is a single 2px-wide ::before on the list container, pinned with position: absolute. Its top and bottom are inset slightly so the line terminates near the first and last dot rather than bleeding out of the container. Each .tl-item::before is the dot — a small circle absolutely positioned left of the card, offset to sit centered on the line. The double-ring glow effect uses a box-shadow with a transparent inner ring (matching the page background) and a colored outer ring, creating visual separation without extra markup. The date label sits above the card, outside the card's background, so it reads as metadata rather than content.
Accessibility & performance
Using a semantic <ul>/<li> structure means screen readers announce the list count and each item in sequence, matching the visual order. Dates as <p> elements sit in reading order before the card title, so the audio experience matches the visual one. Heading levels inside cards (<h3>) should follow the document outline — if the page uses <h2> for section headings, <h3> here is correct. The layout uses only position, padding, and border — no animated properties — so there is no compositor or paint cost at all.