Components
Event rail
Dated events on a rail you drag sideways. The event in the middle is full size, its neighbours shrink and dim with distance, ticks run beneath the cards and each new year's numeral drifts slowly behind them.
pnpm dlx shadcn@latest add @caliper/event-railWhen to use
Use it for
- Company history, release history and milestones on about and changelog pages.
- Any short sequence of dated moments people browse rather than search.
Skip it when
- Activity feeds; use `Timeline`, which is newest first and dense.
- More than a few dozen events; a list with filters serves better.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- Native scrolling
- The rail is a scroll-snapping strip, so touch, trackpads and wheels work as they do everywhere; a mouse can also drag it.
Styling
- Distance sets emphasis
- Size and dimming follow each card's distance from the middle every frame, so the focus moves smoothly while scrolling.
Usage
- A list box
- Arrow keys move to the next event and centre it, Home and End go to the ends, and the centred event is the selected option.
Installation
pnpm dlx shadcn@latest add @caliper/event-rail
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage
This is the code behind the preview above.
import { useState, type ReactNode } from "react" import { EventRail, type RailEvent } from "@/components/ui/event-rail" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } const history: RailEvent[] = [ { id: "founded", date: "March 2019", year: 2019, title: "Founded in Lisbon", description: "Three people, one desk and a payments idea." }, { id: "seed", date: "November 2019", year: 2019, title: "Seed round", description: "Raised to build the first card issuing API." }, { id: "launch", date: "June 2020", year: 2020, title: "Public launch", description: "Opened to every business in Portugal and Spain." }, { id: "hundred", date: "April 2021", year: 2021, title: "100 customers" }, { id: "europe", date: "January 2022", year: 2022, title: "Live across the EU", description: "Licensed in 27 countries." }, { id: "series-a", date: "September 2023", year: 2023, title: "Series A", description: "Doubled the team to build treasury." }, { id: "treasury", date: "May 2024", year: 2024, title: "Treasury ships" }, { id: "today", date: "October 2026", year: 2026, title: "120 people, 9 offices", description: "Still answering support ourselves on Fridays." }, ]; export function EventRailDemo() { const [event, setEvent] = useState("launch"); return ( <Panel title="Our story" context={`Showing ${history.find((item) => item.id === event)?.date}`} className="max-w-3xl"> <EventRail label="Company history" events={history} value={event} onValueChange={setEvent} /> </Panel> ); }
API reference
EventRail
component
Also accepts the native props of its root element and a ref.
Accessibility
It exposes listbox and option semantics, with aria-label, aria-activedescendant, aria-selected and aria-hidden kept in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion
It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.
Related
- TimelineEvents in order with dates and markers.
- Depth timelineTime as distance. The current row is near and sharp, what comes later recedes along the road, and what has shipped slips behind you. Distance follows certainty too: far rows are labelled Planned or Exploring, and every date and status is written out.
- CarouselA swipeable row of slides with controls and dots.