A week-based project timeline: bars packed into lanes over a row of days, with a today marker, week navigation, search and filters.
This field is required
npx shadcn@latest add https://sdk-components.thesqd.com/r/timeline.jsonimport { Timeline } from "@/components/blocks/timeline";"use client";
import { Timeline } from "@/components/blocks/timeline";
const columns = [
{ id: "name", header: "Project" },
{ id: "start", header: "Start", type: "date" },
{ id: "due", header: "Due", type: "date" },
];
const rows = [
{ id: "1", name: "Easter motion pack", start: "2026-09-28", due: "2026-10-01" },
{ id: "2", name: "Christmas series brand", start: "2026-09-30", due: "2026-09-30" },
];
export function Example() {
return (
<Timeline
columns={columns}
rows={rows}
startField="start"
endField="due"
defaultDate={new Date(2026, 8, 28)}
/>
);
}Timeline (.card-surface shell, data-slot="timeline")
├── Toolbar band (.records-toolbar — hidden by hideHeader)
│ ├── icon + title + count pill (a phone keeps only the icon)
│ ├── search → TextField, matches the title and every text column
│ ├── Filters → the filter-builder block, one field per column
│ └── Customize → Popover of Switches: Show avatars, Show badge
├── Navigation band (todayButton / dateNavigation)
│ ├── Today → Button
│ └── prev / DatePicker mode="week" / next
└── Scroller (scroll-fade-x; vertical fade omitted for the sticky header)
├── Day header (sticky, one cell per day, today in the brand colour)
└── Body
├── column rules + today marker (brand line)
├── Lanes → bars packed greedily into the first free lane,
│ clamped to the visible days
│ └── Bar card (.card-surface)
│ ├── title (Tooltip) + ⋯ DropdownMenu
│ ├── "Mon 14 – Wed 16"
│ ├── status Badge + AvatarGroup
│ └── bottom stripe in the colorField option's colour
└── Empty when no bar falls in view
Data
├── columns / rows — the very arrays a DataGridV3 takes
├── startField / endField → date columns ("YYYY-MM-DD"); no end = one day, no start = skipped
├── usersField → user column (roster resolves ids to names)
├── badgeField → status column (label + colour on the Badge)
└── colorField → status / chips column tinting the stripe (defaults to badgeField)This field is required
This field is required
This field is required
| Prop | Type | Default | Description |
|---|---|---|---|
columns | TimelineColumn[] | — | The columns array you give a data grid: `{ id, header, type, options, users }`. |
rows | TimelineRow[] | — | The rows array you give a data grid: `{ id, ...values }`. |
startField | string | "start" | Id of the `date` column holding each bar's first day. A row without one is skipped. |
endField | string | — | Id of the `date` column holding the last day. A row without one is a one-day bar. |
titleField | string | "name" | Column read as the card title. |
usersField | string | — | A `user` column; its `users` roster resolves ids to names and avatars. |
badgeField | string | — | A `status` column; its option `label` and `color` render as a Badge. |
colorField | string | badgeField | A `status` or `chips` column whose option `color` tints the card's bottom stripe. Uses the Badge dot colours. |
title | string | false | "Timeline" | Toolbar heading. `false` drops it. |
icon | ReactNode | false | — | Glyph beside the title. `false` drops it. |
days | number | 7 | Days in view. Previous / next step by this many. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 1 | First day of the week; the range always starts on one. |
date | Date | — | Any day in the visible range, controlled. Defaults to today. |
defaultDate | Date | — | Uncontrolled starting day. Pass one when the server must render bars (today is only known after mount). |
onDateChange | (date: Date) => void | — | Fires with the first day of the new range. |
columnWidth | number | 162 | Minimum width of a day column in px. Columns stretch to fill; below the total the scroller scrolls sideways. |
cardHeight | number | 108 | Height of a bar card in px. |
rowGap | number | 30 | Vertical gap between lanes in px. |
cardInset | number | 20 | Horizontal padding of a card inside its cells in px. |
showToday | boolean | true | Draw the today marker line. |
todayButton | boolean | true | Show the Today button. |
dateNavigation | boolean | true | Show previous / next and the week picker. |
showAvatars | boolean | — | Avatars on each card, controlled. |
defaultShowAvatars | boolean | true | Uncontrolled starting value. |
onShowAvatarsChange | (show: boolean) => void | — | Fires when the Customize switch flips. |
showBadge | boolean | — | Status badge on each card, controlled. |
defaultShowBadge | boolean | true | Uncontrolled starting value. |
onShowBadgeChange | (show: boolean) => void | — | Fires when the Customize switch flips. |
showDates | boolean | true | The "Mon 14 – Wed 16" line on a card. |
showStripe | boolean | true | The coloured stripe along a card's bottom. |
customizable | boolean | true | Render the Customize popover. It only appears when `usersField` or `badgeField` is set. |
searchable | boolean | string | — | Toolbar search field over the title and every text column. A string sets the placeholder. |
search | string | — | The search text, controlled. |
defaultSearch | string | — | The search text on first render, uncontrolled. |
onSearchChange | (search: string) => void | — | Fires with the new text. |
filterable | boolean | — | Show the Filters popover — the filter-builder block over every column. |
filters | FilterGroup | — | The filter tree, controlled. Feed it from a `FilterBuilder` of your own using `filterFields` from `onViewChange`. |
defaultFilters | FilterGroup | — | The filter tree on first render, uncontrolled. |
onFiltersChange | (filters: FilterGroup) => void | — | Fires with the whole tree whenever it changes. |
hideHeader | boolean | false | Drop the toolbar band and keep the timeline; search and filters still work through props. |
onViewChange | (view: { total, matchCount, filtering, filterFields, range: { from, to } }) => void | — | Fires whenever the view changes: the counts, the filter vocabulary and the visible range. The read side of the header API. |
onRowClick | (row: TimelineRow) => void | — | Fires when a card is clicked (alongside the details popover). |
details | boolean | ((row: TimelineRow) => ReactNode) | true | Clicking a card opens a popover with its title, date range, and every column as a term/value row (badges for options, avatars for people). `false` turns it off; a function renders the popover body yourself. |
detailFields | string[] | — | Column ids the details popover lists, in order. Defaults to every column but the title. |
rowMenu | DropdownMenuItemSpec[] | ((row) => DropdownMenuItemSpec[]) | false | — | Items for each card's ⋯ menu. Defaults to an Open item when `onRowClick` is set; `false` hides the button. |
emptyTitle | string | — | Empty state title. Defaults to "Nothing scheduled" ("No matches" while filtering). |
emptyDescription | string | — | Empty state description. |
fill | boolean | false | Stretch to the parent's height; the lanes scroll inside, under the sticky day header. |
className | string | — | Classes for the shell. |