All components solidsubtleoutlinesmmdlgxl
Timeline
Chronological events with indicators, connectors, and per-item content slots.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
123456789101112131415161718192021222324252627282930import { Timeline } from "#components/ui/base-ui/timeline.tsx";
const events = [ { title: "v2.0 released", date: "Aug 12", note: "New theming engine." }, { title: "v1.0 released", date: "Apr 21", note: "First stable release." },];
export function Example() { return ( // variant/size resolve once on Root; parts inherit them via context and // expose data-* attributes. The connector wraps the separator rail and // the indicator dot — omit the separator on the last item to end the rail. <Timeline.Root variant="solid" size="md"> {events.map((event, i) => ( <Timeline.Item key={event.title}> <Timeline.Connector> {i < events.length - 1 ? <Timeline.Separator /> : null} <Timeline.Indicator /> </Timeline.Connector> <Timeline.Content> <Timeline.Title> {event.title} <span>{event.date}</span> </Timeline.Title> <Timeline.Description>{event.note}</Timeline.Description> </Timeline.Content> </Timeline.Item> ))} </Timeline.Root> );}Variants
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
Sizes
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
v2.0 releasedAug 12
New theming engine and 40 fresh blocks.
v1.5 releasedJun 03
Slot recipes for every component.
v1.0 releasedApr 21
First stable release.
Install
1npx stylexblocks add timelineSource
components/ui/base-ui/timeline.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189import * as stylex from "@stylexjs/stylex";import { createSlotRecipeContext } from "#lib/base-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe } from "#lib/recipe-props.ts";import { colors } from '@stylexblocks/ui/tokens/colors.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { radii } from '@stylexblocks/ui/tokens/radii.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const timelineRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', width: sizes.full, '--timeline-thickness': '1px', '--timeline-gutter': '4px', },})
const timelineRootSizes = stylex.create({ sm: { '--timeline-indicator-size': sizes._4, '--timeline-font-size': fontSizes._2xs, }, md: { '--timeline-indicator-size': sizes._5, '--timeline-font-size': fontSizes.xs, }, lg: { '--timeline-indicator-size': sizes._6, '--timeline-font-size': fontSizes.xs, }, xl: { '--timeline-indicator-size': sizes._8, '--timeline-font-size': fontSizes.sm, },})
const timelineItem = stylex.create({ base: { display: 'flex', position: 'relative', alignItems: 'flex-start', flexShrink: 0, gap: spacing._4, },})
const timelineSeparator = stylex.create({ base: { position: 'absolute', borderStartWidth: 'var(--timeline-thickness)', marginInlineStart: 'calc(-1 * var(--timeline-thickness) / 2)', insetInlineStart: 'calc(var(--timeline-indicator-size) / 2)', insetBlock: 0, borderColor: semanticColors.border, },})
const timelineIndicator = stylex.create({ base: { outline: '2px solid var(--sui-colors-bg)', position: 'relative', flexShrink: 0, width: 'var(--timeline-indicator-size)', height: 'var(--timeline-indicator-size)', fontSize: 'var(--timeline-font-size)', display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: radii.full, fontWeight: fontWeights.medium, },})
const timelineIndicatorVariants = stylex.create({ subtle: { backgroundColor: semanticColors.bgMuted, }, solid: { backgroundColor: semanticColors.bgInverted, color: semanticColors.fgInverted, }, outline: { backgroundColor: semanticColors.bg, borderWidth: '2px', },})
const timelineConnector = stylex.create({ base: { alignSelf: 'stretch', position: 'relative', },})
const timelineContent = stylex.create({ base: { paddingBottom: spacing._6, display: 'flex', flexDirection: 'column', width: sizes.full, gap: spacing._2, },})
const timelineTitle = stylex.create({ base: { display: 'flex', fontWeight: fontWeights.medium, flexWrap: 'wrap', gap: spacing._1_5, alignItems: 'center', marginTop: 'var(--timeline-margin)', },})
const timelineTitleSizes = stylex.create({ sm: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { marginTop: spacing._0_5, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, xl: { marginTop: spacing._1_5, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, },})
const timelineDescription = stylex.create({ base: { color: semanticColors.fgMuted, fontSize: fontSizes.xs, lineHeight: lineHeights.xs, },})
export type TimelineVariant = keyof typeof timelineIndicatorVariants
export type TimelineSize = keyof typeof timelineRootSizes
// defineSlotRecipe validates the shape at compile time: default values must// exist in the axis maps, and slot keys must be `styles` or a declared axisconst timelineSlotRecipe = defineSlotRecipe({ slots: { root: { styles: timelineRoot, sizes: timelineRootSizes }, item: { styles: timelineItem }, connector: { styles: timelineConnector }, separator: { styles: timelineSeparator }, indicator: { styles: timelineIndicator, variants: timelineIndicatorVariants }, content: { styles: timelineContent }, title: { styles: timelineTitle, sizes: timelineTitleSizes }, description: { styles: timelineDescription }, }, defaultVariants: { size: "md", variant: "solid" },});
/** * Timeline — recipe and component colocated in one file. `<Timeline.Root * variant size>` resolves the recipe once and every part inherits it through * context; each part composes via `render`. Per item, the connector wraps * the separator line and the indicator dot; omit the separator on the last * item to end the rail. */const { withProvider, withContext } = createSlotRecipeContext(timelineSlotRecipe);
export const Timeline = { Root: withProvider("div", "root", "TimelineRoot"), Item: withContext("div", "item", "TimelineItem"), Connector: withContext("div", "connector", "TimelineConnector"), Separator: withContext("div", "separator", "TimelineSeparator"), Indicator: withContext("div", "indicator", "TimelineIndicator"), Content: withContext("div", "content", "TimelineContent"), Title: withContext("div", "title", "TimelineTitle"), Description: withContext("div", "description", "TimelineDescription"),};