Horizontal Timeline — Free HTML CSS JS Snippet

Horizontal Timeline · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Three CSS states: .done (filled checkmark) / .active (pulse ring) / .upcoming (grey empty)
Pulse ring animation: ::after with scale + opacity keyframe on active dot
Connecting line: absolute position top:53px left/right:40px — spans full track
Horizontal scroll: overflow-x:auto, scrollbar hidden via CSS in Chrome and Firefox
Scroll progress bar below track: scrollLeft/(scrollWidth-clientWidth)×100%
Auto-scroll to active item on load: offsetLeft centring with smooth behaviour
Prev/next buttons: scrollBy with smooth behaviour, 200px per click
Each item min-width: 160px — uniform spacing regardless of label length

About this UI Snippet

Horizontal Timeline — Done/Active/Upcoming States, Pulse Ring, Scroll Progress & Auto-Scroll

Screenshot of the Horizontal Timeline snippet rendered live

A horizontal timeline visualises a sequence of events, milestones, or phases laid out left-to-right — ideal for product roadmaps, company histories, project phases, and onboarding step indicators. Unlike vertical timelines (which work with unlimited height), horizontal timelines must handle overflow — this snippet uses horizontal scroll with hidden scrollbars, a scroll progress indicator, and arrow navigation buttons.

Three item states

Each .tl-item has one of three classes: .done (past milestones — filled indigo dot with checkmark), .active (current milestone — pulsing ring animation), or .upcoming (future milestones — empty grey dot, muted card background). The CSS responds to these classes automatically — no JavaScript state management needed for the visual differentiation.

The pulsing ring animation

The active dot has a .pulse class. Its ::after pseudo-element uses a @keyframes that oscillates scale (0.9 to 1.1) and opacity (0.4 to 1) — creating a breathing glow ring effect. This immediately draws the eye to the current timeline position.

The connecting line

A single .tl-line div is absolutely positioned at top: 53px (the dot centre) with left: 40px and right: 40px. It spans the full track width behind all dots. No JavaScript sizing needed — the absolute positioning with right: 40px automatically extends as the track grows with more items.

Overflow scroll without scrollbar

The .tl-scroll container has overflow-x: auto with scrollbar-width: none (Firefox) and ::-webkit-scrollbar { display: none } (Chrome/Safari). This allows natural scroll gestures on touch devices while hiding the scrollbar for a cleaner desktop appearance. The arrow buttons provide explicit navigation.

Auto-scroll to active item

On page load, scrollTo() centres the active item in the viewport. The timeout ensures the DOM is rendered before measuring offsetLeft. The calculation: item.offsetLeft - containerWidth/2 + itemWidth/2 centres the item precisely.

Connecting to real roadmap data

Replace the static HTML with dynamically generated items: fetch("/api/roadmap").then(r=>r.json()).then(data => { data.milestones.forEach(m => { const item = document.createElement("div"); item.className = "tl-item " + m.state; item.innerHTML = buildItemHTML(m); track.appendChild(item); }); autoScrollToActive(); }). Define buildItemHTML(m) to return the dot and card HTML from the milestone object. The auto-scroll and progress bar work identically with dynamic content.

Accessibility considerations

The horizontal timeline uses a scrollable region. Add role="region" and aria-label="Product roadmap timeline" to .tl-scroll for screen readers. Each .tl-item can include an aria-label with the date and event name. The prev/next buttons already have aria-label attributes. For keyboard users, the scroll container is focusable by default and arrow keys scroll horizontally when focused.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out the centering math by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the auto-scroll offset calculation (offsetLeft minus half the container width plus half the item width) centers the active milestone, or why the connecting line is positioned with a fixed top value tied to the dot's vertical center rather than computed dynamically. The same assistant is useful for optimizing it — ask whether the scroll event listener's progress-bar calculation needs throttling on a timeline with many more items, and whether the 300ms setTimeout before auto-scrolling is a fragile way to wait for layout compared to using a resize observer or double requestAnimationFrame. It's just as handy for extending the timeline: ask it to add keyboard arrow-key navigation between items, support vertical stacking on narrow screens instead of horizontal scroll, or fetch milestones from an API and rebuild the connecting line and progress bar dynamically. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a horizontally-scrolling roadmap timeline in plain HTML, CSS, and JavaScript — no library, no scroll-jacking.

Requirements:
- A horizontally scrollable container holding a row of timeline items, each with a small circular dot and a card below it showing a date, an event title, and a description.
- Each item must carry one of three states — done, active, or upcoming — expressed purely as a CSS class, with done items showing a filled dot with a checkmark icon, active showing a pulsing ring animation around its dot (via a keyframe animating scale and opacity on a pseudo-element), and upcoming showing an empty outlined dot and a muted card background.
- A single absolutely-positioned line spanning behind all the dots at their vertical center, extending automatically as more items are added without needing to recalculate its width in JavaScript.
- Hide the horizontal scrollbar visually in both Firefox and WebKit browsers while keeping the container fully scrollable by touch, mouse wheel, and drag.
- Add previous/next arrow buttons that scroll the container by a fixed pixel amount using smooth scrolling behavior.
- Add a thin progress bar beneath the timeline whose fill width is recalculated on every scroll event as the ratio of current scroll position to the maximum possible scroll distance.
- On page load, after allowing time for layout to settle, automatically smooth-scroll the container so the currently active item is centered in the visible viewport, calculating the target scroll offset from the active item's offsetLeft and width relative to the container's clientWidth.

Step by step

How to Use

  1. 1
    Scroll horizontally or use the ‹ › arrowsThe timeline scrolls horizontally. Use arrow buttons or swipe on mobile. The thin progress bar below shows scroll position within the timeline.
  2. 2
    Update the timeline items and datesEdit each .tl-item div: change .tl-date, .tl-event, and .tl-desc text. The state classes (done/active/upcoming) control the visual appearance automatically.
  3. 3
    Change the current active itemMove class="tl-item active" to the item you want highlighted as current. Remove the .pulse class from the old dot div and add it to the new one. Move the .tl-badge "Current" label similarly.
  4. 4
    Add more timeline itemsDuplicate any .tl-item div and place it at the correct position. The connecting line automatically extends. Each item has min-width: 160px so they space consistently.
  5. 5
    Change the connecting line positionUpdate top: 53px on .tl-line to match the vertical centre of your dots. If you change the dot size or top padding, adjust this value to keep the line at dot height.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component mapping a milestones array to timeline items, or "Tailwind" for a React + Tailwind CSS version.

Real-world uses

Common Use Cases

SaaS product roadmap and release schedule
Display your product roadmap with completed features, current quarter work, and planned future releases. The done/active/upcoming states communicate timeline progress at a glance. Link each card to a detailed changelog or milestone page.
Onboarding progress and setup wizard steps
Show users where they are in an onboarding sequence. Each step is a timeline item — completed steps use the done state, the current step uses active with the pulse ring, and remaining steps use upcoming. Users can see the full sequence and their progress.
Order tracking and shipping status timeline
Show order status from placed → confirmed → packed → shipped → delivered as a horizontal timeline. Each state transition updates the done/active classes. The horizontal layout matches user expectations from major e-commerce tracking pages.
Company history and milestone pages
Showcase company history with founding year, key milestones, product launches, and expansion events. All items use the done state (all past). Add a Current badge to the most recent milestone. The horizontal scroll handles long histories gracefully.
Study horizontal scroll with hidden scrollbar technique
The snippet demonstrates the standard hidden scrollbar pattern for custom horizontal scrollers: overflow-x: auto combined with scrollbar-width: none and ::-webkit-scrollbar { display: none }. This is used in carousels, timelines, and chip rows across the web.
Development sprint and project phase tracking
Display development sprints or project phases on a shared team timeline. The current sprint uses the active state. Team members can see upcoming phases at a glance. Link each card to a sprint board or project doc.

Got questions?

Frequently Asked Questions

Three CSS declarations together hide the scrollbar in all browsers while keeping scrolling functional. For Firefox: scrollbar-width: none on the container. For Chrome, Safari, Edge: .container::-webkit-scrollbar { display: none }. For Internet Explorer: -ms-overflow-style: none. The container still scrolls on touch swipe and mouse wheel horizontally — only the visual scrollbar track is hidden. This is the standard approach for all carousels and custom horizontal scroll areas.

The auto-scroll calculation: const offset = active.offsetLeft - scroll.clientWidth / 2 + active.offsetWidth / 2. This centres the active item in the visible scroll viewport. scroll.scrollTo({ left: offset, behavior: "smooth" }) animates to that position. The setTimeout(fn, 300) delay ensures the layout has rendered and offsetLeft has been computed before the scroll fires. Without the timeout, offsetLeft may be 0 during the synchronous render phase.

Define a milestones array: const milestones = [{ date: "Q1 2025", event: "Foundation", desc: "...", state: "done" }, ...]. Map to HTML: milestones.forEach(m => { const item = document.createElement("div"); item.className = "tl-item " + m.state; item.innerHTML = getItemHTML(m); track.appendChild(item); }). Where getItemHTML(m) returns the inner HTML with dot and card. Run this before the auto-scroll initialisation.

Click "JSX" to download. Define a milestones prop array with date, event, desc, and state fields. Map to timeline item divs with className derived from state. Use useRef for the scroll container. The auto-scroll useEffect depends on the milestones array. The progress bar state uses useState(0) updated by a scroll event listener attached in useEffect with cleanup.