# Timeline Show a series of events or steps in chronological order. Section: components Docs: https://nebula.kaluza.com/components/timeline --- ## When to use - To display a sequence of events in chronological order, for example a customer's account activity or the steps in a completed process - To show the progress of a multi-step task where each step is discrete and has already happened - When the time or sequence relationship between events is meaningful to the user ## When not to use - **For future steps in an incomplete process** - if the user has not yet completed the steps, use a progress indicator or stepper pattern instead. - **For navigation** - a Timeline is a display component, not a navigation component. Use [Tabs](/components/tabs) or page structure for navigable sections. - **For a simple list without meaningful sequence** - use a [List](/components/list) instead. ## Anatomy 1. **Separator** 2. **Node** 3. **Content** ## Basic usage ```tsx

12 Jul 2023
You told us that you're moving out

20 Jul 2023
Your moving day

20 Jul 2023
Submit your final meter readings

``` ## Variants and features ### Custom node By default each item uses a `TimelineDot`, a solid circle in `colors.border`. Replace it with any element using the `node` prop. Custom nodes must have a background colour and `position: relative` so they appear in front of the separator line. All nodes in a timeline must be the same width to keep the content column visually aligned. ```tsx }>

Electricity supply connected

}>

Gas supply connected

``` ### Custom separator By default each item uses a `TimelineSeparator`, a vertical line in `colors.borderMuted`. The separator on the last item is hidden automatically. Replace it using the `separator` prop. When customising a separator, extend the default `TimelineSeparator` with a style override rather than replacing it entirely. This preserves the base positioning and last-item hiding behaviour. The separator is a filled block, so override `background` rather than a border. Here the completed steps use a stronger colour than the default `colors.borderMuted`: ```tsx }>

Move-out date confirmed

}>

Final meter readings submitted

Final bill issued

``` ## Rules and guidance ### Usage - **Timeline** MUST NOT be used for navigation. Rationale: Timeline is a read-only display component. It has no interactive affordances. Use Tabs, links, or page structure for navigable sections. [id: timeline.not-for-navigation; testability: manual] - **Timeline** SHOULD only be used for content where the sequence or time relationship between items is meaningful. Rationale: The visual structure of the Timeline, connected nodes on a vertical axis, implies a meaningful order. Using it for unordered content misleads the user into inferring sequence or causality that does not exist. [id: timeline.chronological-only; testability: manual] ### Composition - **Timeline** WILL hide the separator after the last TimelineItem automatically via CSS. Do not manually force it visible. Rationale: Forcing the last separator visible creates a dangling line that implies a continuation that does not exist. [id: timeline.separator-hidden-automatically; testability: auto] - **Timeline** MUST use nodes of the same width across all TimelineItems. Rationale: The content column alignment depends on the node column being a consistent width. Mixed node widths shift the content column unpredictably across items. [id: timeline.consistent-node-width; testability: manual] - If node prop is a custom element: **TimelineItem node** MUST have a background colour and position relative when a custom node is used. Rationale: The separator line is positioned behind the node using z-index layering. Without a background, the line shows through the custom node. Without position relative, the node renders behind the separator. [id: timeline.custom-node-background; testability: manual] - If separator prop is a custom element: **TimelineItem separator** SHOULD extend the default TimelineSeparator rather than replacing it with an unrelated element. Rationale: TimelineSeparator includes base CSS for positioning and automatic hiding on the last item. Replacing it entirely requires reimplementing this behaviour manually. [id: timeline.custom-separator-extend; testability: manual] ## Accessibility ### In brief Timeline is an ordered list. A screen reader announces the items in markup order, as list items. The dots and separators are empty elements and add nothing to what is announced. ### What Nebula provides - The `Timeline` is an `ol` and each `TimelineItem` an `li`, so the order and the number of items are in the markup. - The dot and the separator hold no text, so a screen reader reads only the item content. ### Accessible design #### Write the date or time in the text of each item The line and the dots show the order, but a screen reader only hears the content. "12 Jul 2023, You told us that you are moving out" says when it happened. WCAG: [1.3.1 Info and Relationships](https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html) #### If a custom node carries meaning, say it in the item text too An Icon used as a node is hidden from screen readers. An electricity icon beside "Electricity supply connected" is decoration, not the only copy. WCAG: [1.1.1 Non-text Content](https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html) [1.4.1 Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) ### Accessible development #### Put the items in chronological order in the markup A screen reader reads the items in the order they are written. Reordering them with CSS changes what a sighted user sees and not what is announced. WCAG: [1.3.2 Meaningful Sequence](https://www.w3.org/WAI/WCAG22/Understanding/meaningful-sequence.html) ### Accessibility testing status Reviewed on 9 October 2026 against v17.0.1. | Test | Status | Tested with | | --- | --- | --- | | Default state | Tested | axe-core via the Storybook test runner (CI) | | Advanced states | Tested | axe-core via the Storybook test runner (CI) | | Keyboard navigation | Manually tested | keyboard (reported by the owner) | | Screen reader | Partially tested | VoiceOver with Chrome (reported by the owner) | What each test covers: - Default state: The component as it first appears, before anything is clicked or changed. - Advanced states: Every other state and variant: error, open, selected, each size and style, and hovering. - Keyboard navigation: Whether every function can be reached and used with the keyboard alone, with a visible focus ring. - Screen reader: Whether a screen reader announces what is on screen correctly: the name, the role and any state. What these statuses mean: - Tested: Every check in this row passed in an automated run that repeats on every pull request. - Manually tested: Every check in this row was run once, by a person or with a tool they started, and all of them passed. It is not re-run on every change. - Partially tested: Some checks in this row were run once and passed; the rest are still to do. - Issue found: A check in this row failed. The ticket is shown beside the status. - Not tested: No check in this row has been run yet. - Not applicable: This row does not apply to the component, for the reason given beside it. The table records which checks were run on this component on its own. It is not a statement of WCAG conformance, and it does not cover how the component is used. ## Properties ### Timeline Also accepts all props from `HTMLAttributes`, `OlHTMLAttributes`. - `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`. - `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it. ### TimelineItem - `separator` (type `ReactNode`, optional, default ``). Connecting line drawn from this item's node to the next. The default separator is hidden on the last item; a custom one is not. - `node` (type `ReactNode`, optional, default ``). Marker shown at the start of the item, for example an icon in place of the default dot. - `children` (type `string | number | boolean | ReactElement> | ReactFragment | ReactPortal`, optional). ### TimelineDot Also accepts all props from `HTMLAttributes`. - `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`. - `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it. ### TimelineSeparator Also accepts all props from `HTMLAttributes`. - `as` (type `ElementType`, optional). Renders the component as a different element or component, e.g. `as="section"`. - `forwardedAs` (type `ElementType`, optional). Like `as`, but passed on to the component being wrapped, for when a styled wrapper should keep its own element and change the one beneath it. ## Styles Reference only; you do not need this to use the component. This is the CSS its styled elements use, with theme tokens shown by name. `${...}` is an interpolation (a theme token, a mixin or another value from the code). A `&[prop]` or `@when ` block holds CSS that applies only under that condition; these are notation, not valid CSS. ### timeline ```css padding: 0; ``` ### timeline-list-item ```css display: flex; position: relative; ``` ### timeline-separator ```css position: absolute; left: 50%; top: 0; width: ${borderWidths.heavy}; transform: translateX(-50%); background: ${colors.borderMuted}; height: 100%; display: block; margin-top: ${space[1]}; ${TimelineListItem}:last-child & { display: none; } ``` ### timeline-node ```css position: relative; margin-right: ${space[2]}; padding-bottom: ${space[3]}; ${mediaQueries.smallAndUp} { margin-right: ${space[3]}; padding-bottom: ${space[4]}; } ${TimelineListItem}:last-child & { padding-bottom: 0; } ``` ### timeline-dot ```css position: relative; width: ${space[5]}; height: ${space[5]}; background: ${colors.border}; border-radius: 50%; overflow: hidden; flex-shrink: 0; ${mediaQueries.smallAndUp} { width: ${space[6]}; height: ${space[6]}; } ``` ### timeline-item-content ```css display: flex; flex-direction: column; justify-content: center; flex: 1; padding-bottom: ${space[3]}; ${mediaQueries.smallAndUp} { padding-bottom: ${space[4]}; } ${TimelineListItem}:last-child & { padding-bottom: 0; } ```