Skip to content
ShineUI
Install

Timeline

Events in order.

<sui-timeline>DataStable

About

Event sequence for audit history, payment lifecycles and onboarding steps. Vertical by default, with the bodies on either side of the spine or alternating, or horizontal; opposite text sits across the line. It is an ordered list, so the order is announced. A severity paints an event's dot in its tint or solid; the accent dot marks one event.

Usage

<sui-timeline></sui-timeline> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-timeline"); el.items = [ { title: "Invoice INV-1043 sent", description: "Emailed to office@silverhillschurch.org", time: "Aug 8, 1:24 PM", icon: "fa-regular fa-paper-plane" }, { title: "Viewed by customer", time: "Aug 9, 8:02 AM", icon: "fa-regular fa-eye" }, { title: "Payment received, $6,420.00", time: "Aug 12, 11:02 AM", icon: "fa-solid fa-check", accent: true }, ]; /* SuiTimelineItem[] */ </script>
import { SuiTimeline } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const items = [ { title: "Invoice INV-1043 sent", description: "Emailed to office@silverhillschurch.org", time: "Aug 8, 1:24 PM", icon: "fa-regular fa-paper-plane" }, { title: "Viewed by customer", time: "Aug 9, 8:02 AM", icon: "fa-regular fa-eye" }, { title: "Payment received, $6,420.00", time: "Aug 12, 11:02 AM", icon: "fa-solid fa-check", accent: true }, ]; /* SuiTimelineItem[] */ return <SuiTimeline items={items} />; }
<script setup> import { SuiTimeline } from "@shineui/vue"; import "@shineui/core/shineui.css"; const items = [ { title: "Invoice INV-1043 sent", description: "Emailed to office@silverhillschurch.org", time: "Aug 8, 1:24 PM", icon: "fa-regular fa-paper-plane" }, { title: "Viewed by customer", time: "Aug 9, 8:02 AM", icon: "fa-regular fa-eye" }, { title: "Payment received, $6,420.00", time: "Aug 12, 11:02 AM", icon: "fa-solid fa-check", accent: true }, ]; // SuiTimelineItem[] </script> <template> <SuiTimeline :items="items" /> </template>
import { Component } from "@angular/core"; import { SuiTimeline } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiTimeline], template: `<sui-timeline [items]="items"></sui-timeline>`, }) export class Example { items = [ { title: "Invoice INV-1043 sent", description: "Emailed to office@silverhillschurch.org", time: "Aug 8, 1:24 PM", icon: "fa-regular fa-paper-plane" }, { title: "Viewed by customer", time: "Aug 9, 8:02 AM", icon: "fa-regular fa-eye" }, { title: "Payment received, $6,420.00", time: "Aug 12, 11:02 AM", icon: "fa-solid fa-check", accent: true }, ]; // SuiTimelineItem[] }

Basic

items is a property; each entry takes a title, a description, a time and a Font Awesome icon. accent paints one dot orange for the event that matters. The list is an ordered list, so a screen reader announces the count and the order.

<sui-timeline></sui-timeline>
// setup(root): properties and events setup(root) { root.querySelector('sui-timeline').items = [ { title: 'Invoice INV-1043 sent', description: 'Emailed to office@silverhillschurch.org', time: 'Aug 8, 1:24 PM', icon: 'fa-regular fa-paper-plane' }, { title: 'Viewed by customer', time: 'Aug 9, 8:02 AM', icon: 'fa-regular fa-eye' }, { title: 'Payment received, $6,420.00', time: 'Aug 12, 11:02 AM', icon: 'fa-solid fa-check', accent: true }, ]; }

Severity

An item's severity paints its dot, and with no icon the dot draws the severity's glyph. severity-fill soft, the default, draws the severity's tint and ink with a ring in its color; solid fills the dot, the way accent does. The dot is not announced, so keep the meaning in the title.

Soft, the default
Solid
<div style="display:flex;gap:32px;flex-wrap:wrap"> <div style="flex:1;min-width:240px;display:grid;gap:12px"> <span style="font:600 12px/1.4 var(--font-sans);color:var(--ink-muted)">Soft, the default</span> <sui-timeline id="tl-soft"></sui-timeline> </div> <div style="flex:1;min-width:240px;display:grid;gap:12px"> <span style="font:600 12px/1.4 var(--font-sans);color:var(--ink-muted)">Solid</span> <sui-timeline id="tl-solid" severity-fill="solid"></sui-timeline> </div> </div>
// setup(root): properties and events setup(root) { root.querySelector('#tl-soft').items = payments(); root.querySelector('#tl-solid').items = payments(); }

href makes the title a link to the record the event is about. render draws the description yourself and returns a string or an element, which is how a feed row carries a tag, a persona or a spoiler beside its words; it wins over description.

<div style="max-width:480px"><sui-timeline id="tl-feed"></sui-timeline></div>
// setup(root): properties and events setup(root) { const node = (tag, props, text) => { const el = root.ownerDocument.createElement(tag); Object.entries(props || {}).forEach(([k, v]) => el.setAttribute(k, v)); if (text) el.textContent = text; return el; }; const row = (children) => { const el = node('span', { style: 'display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap' }); children.forEach((c) => el.appendChild(c)); return el; }; root.querySelector('#tl-feed').items = [ { title: 'LRK-142 moved to In review', href: '#/tasks/142', time: 'Today, 9:14 AM', icon: 'fa-regular fa-circle-check', render: () => row([node('sui-tag', { label: 'In review', severity: 'info', size: 'sm' }), node('span', {}, 'by Dana Whitfield')]) }, { title: 'LRK-139 blocked', href: '#/tasks/139', time: 'Yesterday, 4:02 PM', icon: 'fa-solid fa-ban', render: () => row([node('sui-tag', { label: 'Blocked', severity: 'attention', size: 'sm' }), node('span', {}, 'waiting on the gateway quota')]) }, { title: 'Tom Gordon joined the project', time: 'Aug 28', icon: 'fa-regular fa-user', render: () => node('sui-persona', { name: 'Tom Gordon', secondary: 'Engineer', size: 'xs' }) }, { title: 'Project created', description: 'Gateway migration, target Sep 30', time: 'Aug 4', icon: 'fa-regular fa-folder', accent: true }, ]; }

Activity feed

variant="feed" is the activity list on a record: an item's avatar takes the marker's place, its actor leads the title in the medium weight, the title stays regular and the time sits inline after it. An item without an avatar keeps the dot, so a system event sits among the people; href and render work as before.

<div style="max-width:520px"><sui-timeline id="tl-activity" variant="feed"></sui-timeline></div>
// setup(root): properties and events setup(root) { const tag = (label, severity) => { const el = root.ownerDocument.createElement('sui-tag'); el.setAttribute('label', label); el.setAttribute('severity', severity); el.setAttribute('size', 'sm'); return el; }; root.querySelector('#tl-activity').items = [ { actor: 'Priya Raman', title: 'commented', time: 'Today, 9:14 AM', avatar: { name: 'Priya Raman' }, description: 'The gateway quota is raised. Load test can start on Monday.' }, { actor: 'Dana Whitfield', title: 'moved LRK-142 to In review', href: '#/tasks/142', time: 'Today, 8:40 AM', avatar: { name: 'Dana Whitfield' }, render: () => tag('In review', 'info') }, { actor: 'Tom Gordon', title: 'marked LRK-139 as blocked', href: '#/tasks/139', time: 'Yesterday, 4:02 PM', avatar: { name: 'Tom Gordon' }, render: () => tag('Blocked', 'attention') }, { title: 'Nightly deploy finished', time: 'Yesterday, 2:00 AM', icon: 'fa-solid fa-rocket', description: 'Build 4128 to staging.' }, { actor: 'Tom Gordon', title: 'created the task', time: 'Aug 4', avatar: { name: 'Tom Gordon' } }, ]; }

Alignment

align="start", the default, draws the spine first and the bodies after it; align="end" draws the bodies first; align="alternate" zig-zags them. Under dir="rtl" the whole list mirrors with the text. The second column is drawn only when it holds something, so a plain list stays one column wide.

<div style="display:flex;gap:32px;flex-wrap:wrap"> <div style="flex:1;min-width:220px"><sui-timeline id="tl-end" align="end"></sui-timeline></div> <div style="flex:1;min-width:260px"><sui-timeline id="tl-alt" align="alternate"></sui-timeline></div> </div>
// setup(root): properties and events setup(root) { root.querySelector('#tl-end').items = stages(); root.querySelector('#tl-alt').items = stages(); }

Opposite content

An item's opposite text sits across the line from its body, which is where a stage rail puts its date. It follows align, so an alternating list swaps the pair together.

<div style="max-width:520px"><sui-timeline id="tl-opp"></sui-timeline></div>
// setup(root): properties and events setup(root) { root.querySelector('#tl-opp').items = opposed(); }

Horizontal

The same entries as a stage rail, for a process that runs across a header in reading order. Opposite text sits above the line and the body below it.

<sui-timeline id="tl-row" orientation="horizontal"></sui-timeline>
// setup(root): properties and events setup(root) { root.querySelector('#tl-row').items = opposed(); }

API

<sui-timeline>

Properties

PropertyAttributeTypeDefaultDescription
alignalignSuiTimelineAlign"alternate" | "end" | "start"'start'Where the spine sits in reading order: start (the default) draws the spine first and the bodies after it, end draws the bodies first and aligns them toward the spine, and alternate zig-zags them from one side to the other. Under dir="rtl" the whole list mirrors with the text. Any other value reads as start. Vertical only; a horizontal rail puts the body under the line and any opposite text above it. The second column is drawn only when it holds something, so a plain list is one column and a spine.
itemsproperty onlySuiTimelineItem[][]Events in order: { title, description?, time?, opposite?, icon?, accent?, severity?, href?, render?, actor?, avatar? }[]. href makes the title a link; render returns the description as a string or an element; actor and avatar are the feed variant's person. Property, not attribute.
orientationorientation"horizontal" | "vertical"'vertical'Vertical list (default) or a horizontal rail. Reflected, as on every other element that carries it. align is the other axis: where the spine sits against the bodies.
severityFillseverity-fill"soft" | "solid"'soft'How an item's severity paints its dot. soft, the default, draws the severity's tint and ink with a ring in its color, the way a toast's icon does, so a list of events stays calm and the status still reads. solid fills the dot with the severity's color, the way the accent dot is drawn, for a list where each status should stand out. Items without a severity are untouched. Reflected.
variantvariantSuiTimelineVariant"default" | "feed"'default'default draws stages with heavy titles and the time under the body. feed draws an activity feed: an item's avatar takes the marker's place, its actor leads the title in the medium weight, the title is regular and the time sits inline after it, the way an issue's activity reads. Items keep href, render, icon and accent under both.

Types

SuiRenderResult type
No members.
SuiSeverity type
No members.
SuiTimelineAlign typeWhere the spine sits in reading order: before the bodies (start), after them (end), or between bodies that alternate sides (alternate). Vertical only.
No members.
SuiTimelineAvatar interfaceThe person on a feed row, drawn as a sui-avatar where the marker goes: initials from name or set outright, or an image that falls back to them.
name?string
initials?string
src?string
SuiTimelineItem interfaceOne event on the timeline. accent paints the dot in the accent (the one highlighted event); severity paints it in a status color.
titlestring
description?string
time?stringTimestamp text, e.g. "Aug 12, 11:02 AM". Sits under the body, or after the title on a feed.
opposite?stringText for the other side of the line: the date a stage ran, the person who signed it off.
icon?stringFont Awesome class for the dot glyph; defaults to a hollow dot.
accent?booleanAccent dot: the accent event (payment received, approval granted).
severity?SuiSeverityStatus of the event from the shared ladder, positive, attention, critical, info or neutral: a declined card is critical, a payment received positive. The dot takes the severity's colors, drawn as the timeline's severityFill says, and an item with no icon draws the severity's glyph. Wins over accent. Keep the meaning in the title too, since the dot is not announced.
href?stringMakes the title a link to the record the event is about.
render?() => SuiRenderResultDraws the description yourself: return a string or an element, which is how a feed row carries a tag, a persona or a spoiler. Wins over description; a callback that throws costs only its row, which shows description, and the error is logged once.
actor?stringWho did it, on a feed: drawn in the medium weight before the title, which stays regular.
avatar?SuiTimelineAvatarThe actor's avatar, drawn in the marker's place on a feed. An item without one keeps the dot, which is how a system event sits among the people.
SuiTimelineVariant typedefault is the stage list with heavy titles; feed is an activity feed with an avatar per row, the actor in the medium weight, a regular title and the time inline after it.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also