A horizontal timeline is a great way to show events in chronological order while keeping the UI compact and visually appealing. Below you’ll find a complete, responsive, accessible timeline built with pure HTML and CSS. On wide screens it shows a horizontal timeline with events positioned above and below a center line; on small screens it switches to a vertical layout for better readability.
Live features
- Pure HTML & CSS (no JS required)
- Horizontal layout with alternating items
- Responsive: becomes vertical on narrow screens
- Accessible use of
<time>and ARIA roles - Optional: scroll snapping for better horizontal navigation
HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Responsive Horizontal Timeline</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<section class="timeline" aria-label="Project timeline">
<div class="timeline__container" role="list">
<article class="timeline__item" role="listitem">
<time datetime="2023-01-01">Jan 1, 2023</time>
<h3>Project Kickoff</h3>
<p>Initial planning, scope, and team alignment.</p>
</article>
<article class="timeline__item" role="listitem">
<time datetime="2023-03-15">Mar 15, 2023</time>
<h3>Design Phase</h3>
<p>Wireframes and visual design completed.</p>
</article>
<article class="timeline__item" role="listitem">
<time datetime="2023-06-01">Jun 1, 2023</time>
<h3>Development Start</h3>
<p>Backend and frontend scaffolding implemented.</p>
</article>
<article class="timeline__item" role="listitem">
<time datetime="2023-09-05">Sep 5, 2023</time>
<h3>Beta Release</h3>
<p>Beta released to internal testers for feedback.</p>
</article>
<article class="timeline__item" role="listitem">
<time datetime="2023-12-10">Dec 10, 2023</time>
<h3>Public Launch</h3>
<p>Product launched to the public.</p>
</article>
</div>
</section>
</body>
</html>
CSS (styles.css)
:root{
--bg: #f7f8fb;
--card: #ffffff;
--muted: #6b7280;
--accent: #6c5ce7;
--line: #e6e9ef;
--shadow: 0 6px 18px rgba(30,40,60,0.06);
--radius: 12px;
}
/* Page basics */
*{box-sizing:border-box}
body{
font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
margin:0;
background:var(--bg);
color:#111827;
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
padding:2rem;
}
/* Timeline wrapper */
.timeline{
max-width:1100px;
margin:0 auto;
}
/* Horizontal container */
.timeline__container{
position:relative;
display:flex;
gap:1.5rem;
align-items:center; /* center items vertically relative to timeline line */
padding:2rem;
height:260px; /* fixed height for horizontal view */
overflow-x:auto; /* allow scrolling when narrow */
-webkit-overflow-scrolling:touch;
scroll-behavior:smooth;
border-radius:14px;
background:linear-gradient(180deg, rgba(255,255,255,0.6), rgba(255,255,255,0.4));
box-shadow:var(--shadow);
}
/* Center timeline line (horizontal on wide screens) */
.timeline__container::before{
content:"";
position:absolute;
left:1.5rem;
right:1.5rem;
top:50%;
height:4px;
background:var(--line);
transform:translateY(-50%);
border-radius:4px;
z-index:0;
}
/* Individual timeline event (card) */
.timeline__item{
position:relative;
z-index:1; /* above the center line */
flex:0 0 300px; /* fixed card width, not shrinking */
background:var(--card);
padding:1rem;
border-radius:var(--radius);
box-shadow:var(--shadow);
min-width:260px;
text-align:left;
transition:transform .2s ease, box-shadow .2s ease;
scroll-snap-align:center; /* for scroll snapping */
}
/* hover and focus */
.timeline__item:focus,
.timeline__item:hover{
transform:translateY(-6px);
box-shadow:0 10px 30px rgba(30,40,60,0.12);
outline: none;
}
/* Date/time styling */
.timeline__item time{
display:block;
font-size:0.85rem;
color:var(--muted);
margin-bottom:0.45rem;
}
/* Dot that connects card to center line */
.timeline__item::before{
content:"";
position:absolute;
left:50%;
top:50%;
transform:translate(-50%,-50%);
width:14px;
height:14px;
border-radius:50%;
background:var(--card);
border:4px solid var(--accent);
box-shadow:0 4px 10px rgba(0,0,0,0.06);
}
/* Alternate items above/below the line */
.timeline__item:nth-child(odd){
align-self:flex-start; /* push to top half */
margin-top:-40px; /* slight lift */
}
.timeline__item:nth-child(even){
align-self:flex-end; /* push to bottom half */
margin-bottom:-40px; /* slight lift */
}
/* Small accessible focus style */
.timeline__item:focus{
box-shadow:0 8px 28px rgba(108,92,231,0.18);
transform:translateY(-8px);
}
/* Make it prettier on large screens */
.timeline__item h3{
margin:0 0 .4rem 0;
font-size:1.05rem;
}
.timeline__item p{
margin:0;
color:#374151;
line-height:1.45;
}
/* Optional: enable horizontal snap behavior */
.timeline__container{
scroll-snap-type: x proximity;
}
/* Responsive — switch to vertical timeline on narrow screens */
@media (max-width: 700px){
.timeline__container{
flex-direction:column;
align-items:stretch;
height:auto;
padding:1rem;
}
/* vertical line on the left instead of center horizontal line */
.timeline__container::before{
left:48px;
top:1rem;
bottom:1rem;
right:auto;
width:4px;
height:auto;
transform:none;
}
.timeline__item{
flex: none;
min-width: auto;
margin-left:76px; /* leave space for the vertical line and dot */
margin-bottom:1.25rem;
}
/* move the dot to the left side */
.timeline__item::before{
left:40px;
top:1rem;
transform:translate(-50%,0);
}
/* reset alternating offsets */
.timeline__item:nth-child(odd),
.timeline__item:nth-child(even){
align-self:stretch;
margin-top:0;
margin-bottom:0;
}
}
How it works — short explanation
- Layout strategy
- The
.timeline__containeris a horizontal flex container. Each.timeline__itemis a flex child withflex: 0 0 300pxso cards keep a fixed width and don’t shrink. - A pseudo-element (
::before) on the container draws a horizontal center line.
- The
- Alternating placement
- We use
align-self: flex-startandflex-endon odd/even children to place items above and below the center line so the timeline alternates visually.
- We use
- Responsive behavior
- At smaller width (
@media (max-width: 700px)), container switches toflex-direction: column. The center line changes into a left vertical line (::beforerepositioned), cards stack vertically, and the dot (card::before) shifts to the left.
- At smaller width (
- Accessibility
- Each event uses a
<time datetime="YYYY-MM-DD">which helps machines and screen-readers understand dates. role="list"androle="listitem"improve navigation semantics for some assistive tech.- Focus styles ensure keyboard users can interact.
- Each event uses a
Optional improvements & variations
- Add icons: include an
<svg>or icon inside each.timeline__item::beforeor inside the card. - Animation: add CSS animations (fade/translate) for cards when they come into view (use
@keyframesorintersection observer+ classes if JS allowed). - Vertical-only alternate: remove the alternating offsets if you want a simple horizontal row without up/down alternation.
- Dynamic content: generate the
<article>items via JS when events are stored in JSON. - Accessibility: add
aria-describedbylinking to details panels, or make each item a<button>or<a>to open more details.
Quick tips & best practices
- Use
min-widthandoverflow-x:autoto ensure the horizontal timeline remains usable on narrower screens: users can scroll horizontally. - Use
scroll-snap-typefor a pleasant scrolling experience on touch devices (already included above). - Keep each card width reasonable (260–340px) for readability.
- Make sure color contrast (text vs background) meets WCAG standards.
FAQ
Q: Can I make the timeline purely vertical on desktop?
Yes. Remove the center line rules and the alternating offsets, then set .timeline__container{flex-direction:column;} — the mobile styles already do that.
Q: How do I add more data (time, location, images)?
Extend the .timeline__item HTML with additional elements, e.g., <p class="location">Remote</p> or <img>; then style accordingly.
Q: Is JavaScript required?
No — the provided example is purely HTML/CSS. JS is only needed for advanced behaviors (lazy loading, fetching events dynamically, animated reveal on scroll).