Skip to content
caliper ui docs

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.

Free
pnpm dlx shadcn@latest add @caliper/event-rail

Our story

Showing June 2020

  1. March 2019Founded in LisbonThree people, one desk and a payments idea.
  2. November 2019Seed roundRaised to build the first card issuing API.
  3. June 2020Public launchOpened to every business in Portugal and Spain.
  4. April 2021100 customers
  5. January 2022Live across the EULicensed in 27 countries.
  6. September 2023Series ADoubled the team to build treasury.
  7. May 2024Treasury ships
  8. October 2026120 people, 9 officesStill answering support ourselves on Fridays.
Installs components/ui/event-rail.tsxDepends on none beyond ReactMotion honours reduced motion

When 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

terminal
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.

example.tsx
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.

PropTypeDefaultDescription
label *string—Names the timeline, e.g. "Company history".
events *RailEvent[]—
valuestring—
defaultValuestring—
onValueChange((id: string) => void)—

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.

All Compare & browse examples