Timelines, designed for real interfaces.
Accessible, motion-rich React timelines and progress bars. 14 layouts — from a simple vertical line to roadmaps, Gantt charts and changelogs — on one data model, with zero dependencies.
npm install m2timeline-motion0 runtime dependencies14 layouts≈ 10.8 kB per layoutTypeScript · ESM + CJS · SSR
- CompletedEmail verified
- Completed
Set up workspace
Name, logo and region - In progress3 of 5 invites sent
- Upcoming
Connect data
Postgres, BigQuery or CSV - Upcoming
Go live
Publish your first dashboard
Playground
Tune it live, copy the code
Every control maps to a real prop on the real component. Edit the events in the Data tab, then copy a snippet that matches what you see.
- Completed
main @ 4f2c9e1
- Completed
- Completed1,284 tests passed across 3 shards.
- Completed
- In progressRolling out to 3 of 5 staging pods.Maya Patel (Release captain)
- UpcomingCheckout, sign-in and search flows.
- UpcomingRequires one approval.
import { VerticalTimeline, type TimelineEvent } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';
const events: TimelineEvent[] = [
{
id: 'checkout',
title: 'Checkout & install',
date: '2026-10-18T11:06:00Z',
status: 'completed',
icon: '📦',
category: 'Build',
subtitle: 'main @ 4f2c9e1',
meta: [
{ label: 'Duration', value: '38s' },
],
},
{
id: 'lint',
title: 'Lint & typecheck',
date: '2026-10-18T11:09:00Z',
status: 'completed',
icon: '🧹',
category: 'Build',
meta: [
{ label: 'Duration', value: '1m 12s' },
],
},
{
id: 'tests',
title: 'Unit & integration tests',
date: '2026-10-18T11:12:00Z',
status: 'completed',
icon: '🧪',
category: 'Test',
description: '1,284 tests passed across 3 shards.',
meta: [
{ label: 'Duration', value: '3m 41s' },
],
},
] // …4 more;
export function PipelineTimeline() {
return (
<VerticalTimeline
events={events}
theme="dark"
onEventClick={(event) => console.log('Clicked', event.id)}
/>
);
}
Callback log
Click or expand an event to see callbacks fire.
Layouts
14 layouts, one data model
Every layout reads the same events, so switching is one prop. Pick a layout to see it live, read its code, or open it in the playground.
Vertical <VerticalTimeline />
The classic: a single line with cards on the left or right.
Best for Company milestones, docs, about pages
- Completed
Checkout & install
main @ 4f2c9e1
- Completed
Lint & typecheck
- Completed
Unit & integration tests
1,284 tests passed across 3 shards. - Completed
Production build
- In progressRolling out to 3 of 5 staging pods.Maya Patel (Release captain)
Progress
Bars, steppers and gauges in the same theme
Four progress components for uploads, onboarding, checkout and scores — accessible, animated and themed like the timelines.
- Uploading assets
Progress bar
<ProgressBar /> - Cart, completed
- Shipping, current step
- Payment, not started
- Done, not started
Stepper
<StepProgress />- Chunks6 / 10
Segmented
<SegmentedProgress /> - 70%Score
Gauge
<CircularProgress />
Quick start
Three ways in
Import the stylesheet once. Built for React 18 or 19, and ready for the Next.js App Router.
- Server Components ready. Every component module carries a
'use client'directive; the pure helpers stay server-safe. - Your look. Theme with props or CSS variables such as
--m2t-accentand--m2t-radius.
import { Timeline, type TimelineEvent } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';
const events: TimelineEvent[] = [
{ id: 1, title: 'Company founded', date: '2018-03', description: 'Two founders and a big idea.' },
{ id: 2, title: 'First 100 customers', date: '2019-11', status: 'completed' },
{ id: 3, title: 'Series A', date: '2023-04', highlight: true, details: 'Led by Atlas Partners.' },
{ id: 4, title: 'Global launch', date: '2026-06', status: 'current', progress: 60 },
];
export function QuickStart() {
return <Timeline events={events} variant="alternating" animation="reveal" />;
}
Accessibility
Keyboards, screen readers and calm motion
Real structure and real controls, not decorative markup — and motion that steps aside when the system asks for less.
| Key | Action |
|---|---|
| Tab | Moves between interactive events and controls — every event is a real button. |
| ↑ ↓ ← → | Move focus between events. |
| Home / End | Jump to the first or last event. |
| Enter / Space | Expand or collapse an event’s details. |
- Real structure. A labelled region, ordered lists, real headings and
<time dateTime>elements. - Not colour alone. Status is always conveyed as text as well as colour; steppers expose
aria-current="step". - Reduced motion. Entrance animations and connector fills respect
prefers-reduced-motionautomatically. - Tested. Every layout is checked with axe-core in the package’s test suite.
API
Props at a glance
Every prop is typed. The documentation has the full reference — layout props, callbacks, event data, hooks and CSS variables.
| Prop | Type | Default | Description |
|---|---|---|---|
eventsrequired | ReadonlyArray<TimelineEvent> | — | The data. Safe to update at any time; new items animate in. |
variant | TimelineVariant | 'vertical' | <Timeline> only. Import a layout component directly for smaller bundles. |
theme | 'light' | 'dark' | 'auto' | 'auto' | auto follows prefers-color-scheme. Apps with their own theme toggle should pass their current theme. |
appearance | 'modern' | 'minimal' | 'glass' | 'editorial' | 'vibrant' | 'modern' | Visual style preset. |
accent | string | [string, string] | theme | Accent colour, or a [from, to] pair for gradients. |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Spacing and type scale. |
radius | number | string | appearance | Card corner radius (number = px). |
marker | 'dot' | 'ring' | 'square' | 'diamond' | 'number' | 'none' | 'dot' | Marker shape. Events with an icon render it inside the marker. |
connector | 'solid' | 'dashed' | 'dotted' | 'gradient' | 'none' | 'solid' | Connector line style. |
connectorAnimated | boolean | false | Flowing dashes or a travelling light along the line. |
glow | boolean | true | Glow on current, active and highlighted milestones. |
animation | 'none' | 'fade' | 'rise' | 'slide' | 'scale' | 'blur' | 'reveal' | 'rise' | Entrance preset. Disabled automatically with prefers-reduced-motion. |
Documentation
#Introduction
m2timeline-motion is a library of timeline components for React. Every layout reads the same TimelineEvent data, shares one theme and motion system, and works without a stylesheet framework. Alongside the 14 timeline layouts, the package ships four progress components — progress bars, segmented progress, steppers and gauges.
- Real components with keyboard support, focus states and screen-reader text, not static mockups.
- Entrance animations and connector fills are CSS transitions that respect reduced-motion settings.
- Server rendering is deterministic, so the same markup comes from the server and the browser.
#Installation
Install the package with your package manager. React is a peer dependency (React 18 or 19); there are no other runtime dependencies.
npm install m2timeline-motionImport the stylesheet once — in your entry file or root layout — then import any layout:
import { Timeline } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';#Quick start
The smallest complete example. Change the variant to switch layouts with the same data.
import { Timeline, type TimelineEvent } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';
const events: TimelineEvent[] = [
{ id: 1, title: 'Company founded', date: '2018-03', description: 'Two founders and a big idea.' },
{ id: 2, title: 'First 100 customers', date: '2019-11', status: 'completed' },
{ id: 3, title: 'Series A', date: '2023-04', highlight: true, details: 'Led by Atlas Partners.' },
{ id: 4, title: 'Global launch', date: '2026-06', status: 'current', progress: 60 },
];
export function QuickStart() {
return <Timeline events={events} variant="alternating" animation="reveal" />;
}
#Basic usage
Import a layout component directly so your bundler can leave the others out, give it events, and react to interaction through callbacks. Only id and title are required on each event.
import { VerticalTimeline, type TimelineEvent } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';
const deploys: TimelineEvent[] = [
{ id: 'build', title: 'Production build', date: '2026-10-18T09:12:00Z', status: 'completed' },
{ id: 'staging', title: 'Deploy to staging', date: '2026-10-18T09:20:00Z', status: 'current', progress: 64 },
{ id: 'prod', title: 'Promote to production', status: 'upcoming', details: 'Requires one approval.' },
];
export function DeployTimeline() {
return (
<VerticalTimeline
events={deploys}
onEventClick={(event) => console.log('Selected', event.id)}
/>
);
}#Choosing a layout
Every layout is its own component. The gallery above shows each one live; this table summarizes what they are for.
| Component | variant | Best for |
|---|---|---|
VerticalTimeline | vertical | Company milestones, docs, about pages |
VerticalTimeline align="alternate" | alternating | Histories, landing pages, portfolios |
HorizontalTimeline | horizontal | Release trains, hero sections, compact overviews |
RoadmapTimeline | roadmap | Public roadmaps, planning, OKRs |
GanttTimeline | gantt | Project plans, delivery schedules |
CareerTimeline | career | Résumés, portfolios, team bios |
HistoryTimeline | history | Company history, museums, long reads |
ChangelogTimeline | changelog | Release notes, product updates |
StoryTimeline | story | Brand stories, case studies, annual reports |
MinimalTimeline | minimal | Professional sites, CVs, footnotes |
CardTimeline | cards | Events, case studies, media-rich updates |
ZigzagTimeline | zigzag | Journeys, processes, infographics |
StepperTimeline | stepper | Onboarding, order tracking, wizards |
FeedTimeline | feed | Dashboards, audit logs, notifications |
CustomTimeline | custom | Your own markup with the engine’s ordering, search, motion and keyboard support |
Reverse chronological order works with every layout: pass order="desc".
#Customization & theming
Start with props: theme, appearance (modern, minimal, glass, editorial, vibrant), accent, density, marker and connector.
<RoadmapTimeline
events={roadmap}
theme="dark"
appearance="minimal"
accent={['#6d5bd0', '#3b82f6']}
density="compact"
marker="ring"
connector="dashed"
/>For anything else, every visual decision is a CSS custom property on .m2t:
/* All timelines */
.m2t {
--m2t-accent: #6d5bd0;
--m2t-radius: 10px;
--m2t-font: 'Figtree', system-ui, sans-serif;
--m2t-sticky-top: 64px; /* below your fixed header */
}
/* One timeline: <VerticalTimeline className="release-notes" … /> */
.release-notes .m2t-card {
border-width: 2px;
}Stable class names (.m2t-card, .m2t-marker, .m2t-title…) and data attributes ([data-status], [data-active]) are part of the public API. Use the classNames prop to attach utility classes to internal parts. To replace rendering, use renderEvent, renderMarker or renderDate, CustomTimeline, or the headless useTimeline hook:
import { CustomTimeline, type TimelineEvent } from 'm2timeline-motion';
import 'm2timeline-motion/styles.css';
const sprints: TimelineEvent[] = [
{ id: 's1', title: 'Sprint 1 — Foundations', date: '2026-09-01', status: 'completed', progress: 100 },
{ id: 's2', title: 'Sprint 2 — Editor', date: '2026-09-15', status: 'completed', progress: 100 },
{ id: 's3', title: 'Sprint 3 — Collaboration', date: '2026-09-29', status: 'current', progress: 45 },
{ id: 's4', title: 'Sprint 4 — Polish', date: '2026-10-13', status: 'upcoming' },
];
/** Bring your own markup: the engine still handles ordering, search, expansion, motion and theming. */
export function SprintBoard() {
return (
<CustomTimeline
events={sprints}
search
listClassName="sprint-grid"
renderItem={(item, { parts, isActive }) => (
<div className={isActive ? 'sprint sprint--active' : 'sprint'}>
<div className="sprint__head">
{parts.marker}
{parts.date}
</div>
{parts.title}
<progress max={100} value={item.progress ?? 0} aria-label={`${item.progress ?? 0}% complete`} />
</div>
)}
/>
);
}
import { TimelineCard, TimelineFrame, itemAttrs, useTimeline, type TimelineEvent } from 'm2timeline-motion';
export function Headless({ events }: { events: TimelineEvent[] }) {
const timeline = useTimeline({ events, order: 'desc' });
return (
<TimelineFrame events={events} controller={timeline} layout="kanban" search>
<ul className="my-columns">
{timeline.visible.map((item, i) => (
<li key={item.key} {...itemAttrs(item, i)}>
<TimelineCard item={item} index={i} expanded={timeline.isExpanded(item.key)} />
</li>
))}
</ul>
</TimelineFrame>
);
}
#Animation & transitions
Items reveal as they enter the viewport using one IntersectionObserver per timeline; scrolling never re-renders React. Presets animate only opacity, transform, filter and clip-path.
<VerticalTimeline
events={events}
animation="blur" // none | fade | rise | slide | scale | blur | reveal
duration={500}
stagger={90}
scrollProgress // line fills as you scroll
connectorAnimated // travelling light along the line
/>#TypeScript
The package is written in TypeScript and ships TypeScript declarations for ESM and CommonJS. Props are discriminated by variant, so each layout’s own options autocomplete, and TimelineEvent<TData> carries your own typed data through to render callbacks.
import type { TimelineEvent, TimelineProps, LayoutVariant } from 'm2timeline-motion';
type Release = { downloads: number };
const releases: TimelineEvent<Release>[] = [
{ id: '1.2.0', title: 'Search', version: '1.2.0', data: { downloads: 1840 } },
];
// Every layout except "custom" (which requires renderItem) — handy for switchers.
const layout: LayoutVariant = 'changelog';#Accessibility
- A labelled region, ordered lists, real headings and
<time dateTime>elements. - Interactive events are buttons with
aria-expandedandaria-controls; collapsed details are hidden from assistive technology. - Arrow keys, Home and End move focus between events; horizontal and Gantt views scroll with the keyboard.
- Status is always conveyed as text as well as colour. Steppers expose
aria-current="step". - Every layout is checked with axe-core in the package’s test suite.
#Frameworks & SSR
Built for React 18 or 19. Works with the Next.js App Router: every component module carries a 'use client' directive, so you can render timelines from Server Components, while the pure helpers stay server-safe. Dates are formatted in UTC by default, and time-relative output (“3 hours ago”, today lines) renders after hydration, so server and client markup match.
// app/page.tsx — a React Server Component. No 'use client' needed here:
// every timeline component is already a client component, and the data is plain serialisable JSON.
import { ChangelogTimeline, type TimelineEvent } from 'm2timeline-motion';
import { ReleaseTimeline } from './release-timeline';
async function getReleases(): Promise<TimelineEvent[]> {
// e.g. `await fetch('https://api.example.com/releases', { next: { revalidate: 3600 } })`
return [
{ id: '2.1.0', version: '2.1.0', title: 'Dark mode', date: '2026-10-01', changes: [{ type: 'added', text: 'System theme' }] },
{ id: '2.0.0', version: '2.0.0', title: 'New API', date: '2026-08-12', changes: [{ type: 'breaking', text: 'v1 removed' }] },
];
}
export default async function Page() {
const releases = await getReleases();
return (
<main>
{/* Serialisable props only → render directly from the server component */}
<ChangelogTimeline events={releases} />
{/* Function props (callbacks, render functions) → wrap in your own client component */}
<ReleaseTimeline events={releases} />
</main>
);
}
'use client';
import { VerticalTimeline, type TimelineEvent } from 'm2timeline-motion';
export function ReleaseTimeline({ events }: { events: TimelineEvent[] }) {
return (
<VerticalTimeline
events={events}
onEventClick={(event) => console.log('Opened release', event.id)}
formatDate={(date) => date.toLocaleDateString('en-GB', { timeZone: 'UTC' })}
/>
);
}
#Examples & recipes
Complete, copy-ready recipes. Each one is type-checked and server-rendered against the packed package in the release checks.
A public roadmap with search and filters
Quarter lanes are derived from dates automatically. Status drives colors, progress bars and the “Now” lane.
Q2 2026
3/3- Completed
SSO & SCIM provisioning
Okta, Azure AD and Google Workspace.- security
- Completed
Audit log export
Stream events to S3 or your SIEM. - Completed
Usage-based billing
Metered plans with real-time usage.
Q3 2026
Now0/2- In progress
Workflow builder
Drag-and-drop automations with 40+ triggers.Iris Novak, Tom Reyes - DelayediOS & Android companion apps.
Q4 2026
0/2- In progress
AI assistant (beta)
Ask questions about your data in plain language. - Upcoming
Public API v2
GraphQL, webhooks and typed SDKs.
Q1 2027
0/1- Upcoming
EU data residency
Frankfurt region with full isolation.
Q2 2027
0/2- Upcoming
Marketplace
Third-party integrations and templates. - Upcoming
Offline mode
Work without a connection, sync later.
API reference
#Shared props
Accepted by every layout component and by <Timeline>.
| Prop | Type | Default | Description |
|---|---|---|---|
eventsrequired | ReadonlyArray<TimelineEvent> | — | The data. Safe to update at any time; new items animate in. |
variant | TimelineVariant | 'vertical' | <Timeline> only. Import a layout component directly for smaller bundles. |
theme | 'light' | 'dark' | 'auto' | 'auto' | auto follows prefers-color-scheme. Apps with their own theme toggle should pass their current theme. |
appearance | 'modern' | 'minimal' | 'glass' | 'editorial' | 'vibrant' | 'modern' | Visual style preset. |
accent | string | [string, string] | theme | Accent colour, or a [from, to] pair for gradients. |
density | 'compact' | 'comfortable' | 'spacious' | 'comfortable' | Spacing and type scale. |
radius | number | string | appearance | Card corner radius (number = px). |
marker | 'dot' | 'ring' | 'square' | 'diamond' | 'number' | 'none' | 'dot' | Marker shape. Events with an icon render it inside the marker. |
connector | 'solid' | 'dashed' | 'dotted' | 'gradient' | 'none' | 'solid' | Connector line style. |
connectorAnimated | boolean | false | Flowing dashes or a travelling light along the line. |
glow | boolean | true | Glow on current, active and highlighted milestones. |
animation | 'none' | 'fade' | 'rise' | 'slide' | 'scale' | 'blur' | 'reveal' | 'rise' | Entrance preset. Disabled automatically with prefers-reduced-motion. |
duration | number | 600 | Animation duration in ms. |
stagger | number | 70 | Delay between items that enter the viewport together, in ms. |
animateOnce | boolean | true | Only animate the first time an item enters the viewport. |
scrollProgress | boolean | false | Fill the connector as you scroll (vertical and story layouts). |
order | 'none' | 'asc' | 'desc' | 'none' | Sort by date; desc gives reverse chronological order. |
groupBy | false | 'year' | 'quarter' | 'month' | 'day' | 'category' | 'group' | (item) => string | per layout | Group headings, lanes or phases. |
expandable | boolean | 'single' | true | Events with details expand. single keeps one open at a time. |
expandedIds / defaultExpandedIds | ReadonlyArray<string | number> | — | Controlled / uncontrolled expansion. |
activeId / defaultActiveId | string | number | null | — | Controlled / uncontrolled active event. |
trackActive | boolean | false | Mark the event in the middle of the viewport as active while scrolling. |
autoScroll | boolean | false | Scroll the active event into view when activeId changes. |
keyboardNavigation | boolean | true | Arrow keys, Home and End move focus between events. |
search | boolean | { placeholder?, label? } | false | Built-in search field. Pair with query / defaultQuery to control it. |
filters | boolean | { categories?, label? } | false | Category chips from event.category. Pair with selectedCategories to control them. |
jumpTo | boolean | false | Buttons that jump to each group. |
pageSize | number | — | Render this many items, then a “Show more” button. |
renderEvent / renderMarker / renderDate | (event, ctx) => ReactNode | — | Replace parts of the default rendering. |
formatDate | (date, precision, event) => string | — | Custom date formatter (receives the parsed UTC date). |
locale / timeZone | string / string | 'en-US' / 'UTC' | Built-in date formatting. UTC keeps server and client output identical. |
labels | Partial<TimelineLabels> | English | Every UI string, for i18n. |
emptyState | ReactNode | — | Content shown when there are no events. |
headingLevel | 2 | 3 | 4 | 5 | 6 | 3 | Heading level for event titles (groups use one level higher). |
className / style / classNames | — | — | Root class and style, plus per-part class names (card, marker, title…). |
#Callbacks
| Prop | Type | Default | Description |
|---|---|---|---|
onEventClick | (event, { item, index, domEvent }) => void | — | An event was clicked or activated with Enter / Space. |
onActiveChange | (id, event) => void | — | The active event changed (click, keyboard or trackActive). |
onExpandedChange | (ids) => void | — | The set of expanded events changed. |
onQueryChange | (query) => void | — | The built-in search text changed. |
onCategoriesChange | (categories) => void | — | The selected category chips changed. |
#Event data
The TimelineEvent shape. Invalid input never throws: missing or duplicate ids, invalid dates and out-of-range progress are repaired and reported once as development warnings.
| Field | Type | Default | Description |
|---|---|---|---|
idrequired | string | number | — | Stable unique id, used for keys, expansion and active state. |
titlerequired | ReactNode | — | Event title. Becomes a button when the event is interactive. |
date | string | number | Date | — | "2024", "2024-03", "2024-03-18", ISO strings, Date or timestamps. Integers −9999…9999 are years. |
endDate | string | number | Date | 'present' | — | End of a range, or "present" for ongoing items. |
dateLabel | ReactNode | — | Overrides the formatted date. |
subtitle / location / description | ReactNode | — | Secondary text. |
details | ReactNode | — | Content revealed when the event is expanded. |
status | 'completed' | 'current' | 'upcoming' | 'delayed' | 'blocked' | 'cancelled' | — | Drives colours, marker icons, connector fill and screen-reader text. |
progress | number | — | 0–100; clamped. Progress bar, Gantt fill, roadmap completion. |
category / group / tags | string / string / string[] | — | Filter chips, grouping (phases, companies, eras) and tags. |
version / changes | string / { type, text }[] | — | Changelog version and grouped change lists. |
icon / image | ReactNode / string | { src, alt, width?, height?, caption? } | — | Marker icon; cover, inline media or company logo. |
badges / meta / people | see TimelineEvent | — | Badges, key–value metadata and owners / avatars. |
links / actions | { label, href }[] / { label, onClick | href, variant }[] | — | Footer links and buttons. |
color / highlight / disabled | string / boolean / boolean | — | Per-event accent, special-milestone glow, non-interactive. |
searchText / data | string / TData | — | Plain text for search when the title is JSX; your own typed payload. |
#Layout props
Options specific to one layout, in addition to the shared props.
| Component | Prop | Type | Default | Description |
|---|---|---|---|---|
VerticalTimeline | align | 'left' | 'right' | 'alternate' | 'left' | Card side; `alternate` zig-zags around a centred line. |
VerticalTimeline | datePlacement | 'card' | 'opposite' | 'card' ('opposite' when alternating) | Show the date in the card or across the line. |
VerticalTimeline | startSide | 'left' | 'right' | 'right' | First card side for `align="alternate"`. |
HorizontalTimeline | cardPosition | 'below' | 'above' | 'alternate' | 'below' | Where cards sit relative to the line. |
HorizontalTimeline | itemWidth | string | number | clamp(15rem, 30cqi, 19rem) | Width of each item. |
HorizontalTimeline | showNavigation | boolean | true | Previous / next buttons and the scroll indicator. |
HorizontalTimeline | startAt | 'start' | 'end' | 'current' | 'start' | Initial scroll position. |
HorizontalTimeline | dragToScroll | boolean | true | Click-and-drag scrolling with a mouse. |
RoadmapTimeline | showOverview | boolean | true | Overall progress track with one segment per phase. |
RoadmapTimeline | laneWidth | string | '17rem' | Minimum lane width. |
GanttTimeline | scale | 'auto' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'auto' | Time-axis unit. |
GanttTimeline | today | TimelineDateInput | false | client clock | Today line; pass a date to render it on the server, `false` to hide it. |
GanttTimeline | labelWidth / minWidth | string / number | '15rem' / 760 | Task column width; chart width before it scrolls. |
GanttTimeline | showPeople | boolean | true | Owner avatars next to task names. |
CareerTimeline | showDuration | boolean | true | Computed tenure (“2 yrs 3 mos”). |
HistoryTimeline | stickyLabels | boolean | true | Keep era / year headings pinned while events scroll. |
ChangelogTimeline | showBump / showLatest | boolean / boolean | true / true | Major / Minor / Patch detection; “Latest” badge. |
StoryTimeline | mediaPosition | 'alternate' | 'left' | 'right' | 'alternate' | Image side. |
StoryTimeline | showNavigation / showProgress | boolean / boolean | true / true | Sticky chapter navigation; reading-progress bar. |
CardTimeline | minCardWidth | string | '17rem' | Minimum card width in the responsive grid. |
ZigzagTimeline | columns / minColumnWidth | 2 | 3 | 4 / number | 3 / 240 | Milestones per row; fewer columns on narrow containers. |
StepperTimeline | orientation | 'horizontal' | 'vertical' | 'horizontal' | Switches to vertical automatically in narrow containers. |
StepperTimeline | showSummary | boolean | true | “Step 3 of 5” summary with an overall progress bar. |
FeedTimeline | relativeTime | boolean | true | “3 hours ago” times, rendered after hydration. |
CustomTimeline | renderItem | (item, ctx) => ReactNode | required | Renders each list item; `ctx.parts` holds ready-made marker, card, date, title and connector. |
CustomTimeline | listClassName / itemClassName / layoutName | string / string | fn / string | — / — / 'custom' | Classes for your own layout CSS. |
#Hooks & utilities
| Export | Type | Default | Description |
|---|---|---|---|
useTimeline(options) | TimelineController | — | Headless engine: ordering, search, filters, grouping, pagination, expansion and active state. |
TimelineFrame / TimelineCard / TimelineMarker / itemAttrs | components / helper | — | Building blocks for custom layouts with the same theming, motion and keyboard support. |
TimelineToolbar / TimelineDate / TimelineTitle / StatusBadge… | components | — | Individual parts of the default rendering. |
useNow() / useReducedMotion() | number | null / boolean | — | Hydration-safe clock and reduced-motion preference. |
parseDate / formatParsedDate / formatRelative | functions | — | Date helpers used by the layouts (server-safe). |
resolveEvents / sortItems / filterItems / groupItems | functions | — | The pure data pipeline (server-safe). |
detectBump / parseVersion | functions | — | Semantic-version helpers used by the changelog. |
defaultLabels | TimelineLabels | — | Default English UI strings to extend for i18n. |
#CSS variables
| Variable | Type | Default | Description |
|---|---|---|---|
--m2t-accent / --m2t-accent-2 | color | #4f46e5 / #db2777 | Accent and gradient end. |
--m2t-surface / -2 / -3 | color | white → greys | Card and subtle backgrounds. |
--m2t-text / --m2t-text-2 / --m2t-text-muted | color | — | Text colours (AA contrast on surfaces). |
--m2t-border / --m2t-line | color | — | Borders and the connector line. |
--m2t-success / -warning / -danger / -info | color | — | Status tones. |
--m2t-radius / --m2t-line-width | length | 14px / 2px | Shape. |
--m2t-marker-size / -md / -lg | length | 14 / 28 / 36px | Marker sizes. |
--m2t-font / --m2t-font-display / --m2t-font-mono | font | inherit | Fonts; timelines inherit your app font. |
--m2t-gap / --m2t-pad-y / --m2t-pad-x | length | density | Spacing. |
--m2t-duration / --m2t-ease | time / easing | 600ms | Motion. |
--m2t-sticky-top | length | 1rem | Offset for sticky headings (set it to your header height). |
Help
#Troubleshooting
- The timeline renders unstyled.
- Import
m2timeline-motion/styles.cssonce in your app. - “Functions cannot be passed directly to Client Components”.
- A callback was passed from a Server Component. Move that timeline into a file with
'use client'. - Dates are off by one day.
- Date-only strings are UTC calendar dates and format as written. For timestamps, pass
timeZoneor a customformatDate. - Sticky headings hide behind my header.
- Set
--m2t-sticky-topto your header height. - Search doesn’t find events with JSX titles.
- Add a plain-text
searchTextto those events.
#FAQ
- Does it work without the stylesheet framework I use?
- Yes. The package ships plain CSS scoped to its own class names, so it sits next to Tailwind, CSS Modules or anything else.
- How do I keep bundles small?
- Import layouts individually. Each layout is its own module and the package marks only CSS as having side effects. Measured sizes are listed under Package information.
- Can I use my own markup?
- Yes — use
renderEventfor the card body,CustomTimelinefor whole items, or the headless useTimeline hook for full control. - Is it ready for very long timelines?
- Items are memoised and search runs at a lower priority while you type. For hundreds of events, render in pages with
pageSize.
#Changelog
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses Semantic Versioning.
Versioning policy
- **Major**: breaking changes to props, the
TimelineEventshape, exported types, documented CSS custom properties, class names or data attributes. - **Minor**: new layouts, props, tokens or features that are backwards compatible.
- **Patch**: bug fixes, accessibility fixes and visual refinements that do not change the public API.
Pre-1.0 releases may contain breaking changes in minor versions; they are always called out below.
[Unreleased]
[0.1.0] - 2026-10-18
Added
- 14 layouts:
VerticalTimeline(left / right / alternating),HorizontalTimeline,RoadmapTimeline,GanttTimeline,CareerTimeline,HistoryTimeline,ChangelogTimeline,StoryTimeline,MinimalTimeline,CardTimeline,ZigzagTimeline,StepperTimeline,FeedTimelineandCustomTimeline, plus the unified<Timeline variant>component. - Headless
useTimelinehook and building blocks (TimelineFrame,TimelineCard,TimelineMarker,TimelineToolbar, parts and utilities). - Themes (light / dark / auto), appearances (modern, minimal, glass, editorial, vibrant), density, radius, marker shapes, connector styles, animated connectors and glow.
- Scroll-reveal presets (fade, rise, slide, scale, blur, reveal), stagger, scroll-linked progress line, scroll-spy and auto-scroll; full
prefers-reduced-motionsupport. - Search, category filters, jump-to navigation, pagination, expandable details, controlled / uncontrolled active and expansion state, keyboard navigation and i18n labels.
- Progress components:
ProgressBar(solid, segmented, dots, bars, slanted, chevron and outlined tracks; loading / success / error statuses; info tooltips; helper text; value placements incl. tooltip and inside; stripes, thumb, glow, buffer, indeterminate, milestones, cancel),SegmentedProgress(per-segment status with tooltips),StepProgress(numbered, dots and icon steps) andCircularProgress(rings and gauges). - ESM + CommonJS builds with type declarations, per-module
'use client'boundaries, CSS stylesheet.
Package
#Package information
Install
npm install m2timeline-motion- Package
m2timeline-motion- Version
- 0.1.0
- License
- MIT
- Peer dependencies
react,react-dom^18.0.0 || ^19.0.0- Runtime dependencies
- 0
- Formats
- ESM, CommonJS, TypeScript declarations, one CSS file
- Source repository
- Not public yet
Bundle size
- One layout (VerticalTimeline)
- 10.8 kB
- All 14 layouts (<Timeline>)
- 18.2 kB
- ProgressBar
- 2.2 kB
- styles.css
- 16 kB
Minified and gzipped with React external, measured by the package’s size check.
More from M2Toolkit
Browse all npm packages on M2Toolkit