Timeline
tier 2 — classes
A record of things that happened, with when — an audit trail, a run history, a comment thread.
It differs from .steps in being open-ended: if the
reader can be “on” one of them, it is steps.
Timeline
Two spans per entry: .timeline-when and .timeline-what.
data-kind — go, warn, danger,
info — colours a marker for an entry that mattered.
- 03:15 Approved and written to the warehouse
- 02:58 Priority raised to P2 by the housekeeper
- 02:41 Rerouted to EU-West
- 01:12 First reply attempt failed — mailbox unavailable
- 00:31 Order opened from an e-mail
<ul class="timeline" style="max-width:460px">
<li data-kind="go">
<span class="timeline-when">03:15</span>
<span class="timeline-what">Approved and written to the warehouse</span>
</li>
<li data-kind="warn">
<span class="timeline-when">02:58</span>
<span class="timeline-what">Priority raised to P2 by the housekeeper</span>
</li>
<li data-kind="info">
<span class="timeline-when">02:41</span>
<span class="timeline-what">Rerouted to EU-West</span>
</li>
<li data-kind="danger">
<span class="timeline-when">01:12</span>
<span class="timeline-what">First reply attempt failed — mailbox unavailable</span>
</li>
<li>
<span class="timeline-when">00:31</span>
<span class="timeline-what">Order opened from an e-mail</span>
</li>
</ul>
Icon markers
A .timeline-mark child replaces the dot, for a record where the marker
is the information — a robot for “the agent worked it”, a lock
for “it needs a person”. The gutter is set by the list, so one
marked entry widens every row and the connector stays straight.
- 03:15 Decided — the reply went out unchanged
- 02:58 Held for a person: the mailbox is shared
- 02:41 The agent drafted a reply and cited two articles
- 00:31 Opened from an e-mail to the service desk
<ul class="timeline" style="max-width:460px">
<li data-kind="go">
<span class="timeline-mark"><i class="ri-check-line" aria-hidden="true"></i></span>
<span class="timeline-when">03:15</span>
<span class="timeline-what">Decided — the reply went out unchanged</span>
</li>
<li data-kind="warn">
<span class="timeline-mark"><i class="ri-lock-line" aria-hidden="true"></i></span>
<span class="timeline-when">02:58</span>
<span class="timeline-what">Held for a person: the mailbox is shared</span>
</li>
<li>
<span class="timeline-mark"><i class="ri-robot-2-line" aria-hidden="true"></i></span>
<span class="timeline-when">02:41</span>
<span class="timeline-what">The agent drafted a reply and cited two articles</span>
</li>
<li>
<span class="timeline-mark"><i class="ri-mail-line" aria-hidden="true"></i></span>
<span class="timeline-when">00:31</span>
<span class="timeline-what">Opened from an e-mail to the service desk</span>
</li>
</ul>
The entry still happening
data-state="current" marks the head of a live record — a job still running, a
reply still being drafted.
- now Drafting a reply against the two cited articles
- 14:07 Matched to the “returns after 30 days” policy
- 14:06 Order opened from an e-mail
<ul class="timeline" style="max-width:460px">
<li data-state="current">
<span class="timeline-when">now</span>
<span class="timeline-what">Drafting a reply against the two cited articles</span>
</li>
<li data-kind="info">
<span class="timeline-when">14:07</span>
<span class="timeline-what">Matched to the “returns after 30 days” policy</span>
</li>
<li>
<span class="timeline-when">14:06</span>
<span class="timeline-what">Order opened from an e-mail</span>
</li>
</ul>
Entries with a body
.timeline-body is the supporting block under the headline — the error a retry
returned, the fields that changed, the chips for what it touched. A marker is opaque, and
--timeline-surface reads --surface-here, so a timeline in a card,
a modal or a drawer is right already; on a surface your app paints, set
--timeline-surface on the list.
-
14:52
Automatic reply could not be delivered
550 5.2.2 Mailbox full — quota 2048 MB exceededRetried twice, ten minutes apart. Both attempts returned the same code.
-
14:41
Held for a person
The mailbox is shared, so nobody owns the reply. Assigned to the on-call queue rather than to a name.
-
14:38
Classified and tagged
Exchange Quota Contoso Logistics
<div class="card" style="max-width:520px">
<div class="card-head">
<strong>TCK-4182 — Mailbox quota exceeded on EXCH-02</strong>
</div>
<div class="card-body">
<ul class="timeline">
<li data-kind="danger">
<span class="timeline-mark"><i class="ri-error-warning-line" aria-hidden="true"></i></span>
<span class="timeline-when">14:52</span>
<span class="timeline-what">Automatic reply could not be delivered</span>
<div class="timeline-body">
<p><code>550 5.2.2 Mailbox full — quota 2048 MB exceeded</code></p>
<p>Retried twice, ten minutes apart. Both attempts returned the same code.</p>
</div>
</li>
<li data-kind="warn">
<span class="timeline-mark"><i class="ri-lock-line" aria-hidden="true"></i></span>
<span class="timeline-when">14:41</span>
<span class="timeline-what">Held for a person</span>
<div class="timeline-body">
The mailbox is shared, so nobody owns the reply. Assigned to the on-call
queue rather than to a name.
</div>
</li>
<li>
<span class="timeline-mark"><i class="ri-price-tag-3-line" aria-hidden="true"></i></span>
<span class="timeline-when">14:38</span>
<span class="timeline-what">Classified and tagged</span>
<div class="timeline-body">
<div class="chip-set">
<span class="chip">Exchange</span>
<span class="chip">Quota</span>
<span class="chip">Contoso Logistics</span>
</div>
</div>
</li>
</ul>
</div>
</div>
Timestamps in a gutter
.timeline--aside moves the “when” into a right-aligned column of
its own — reach for it when the reader scans by time, because a column of times can
be read down. The gutter is --timeline-aside; not for a narrow
column, where the entry text then wraps to three words a line.
- 08:12:04 Run finished — 1 340 rows written, 0 rejected
- 08:11:58 Retried the ledger batch once after a lock timeout
- 08:09:31 Loaded the incremental slice since yesterday 08:00
- 08:09:30 Run started by the nightly schedule
<ul class="timeline timeline--aside" style="max-width:520px">
<li data-kind="go">
<span class="timeline-when">08:12:04</span>
<span class="timeline-what">Run finished — 1 340 rows written, 0 rejected</span>
</li>
<li data-kind="warn">
<span class="timeline-when">08:11:58</span>
<span class="timeline-what">Retried the ledger batch once after a lock timeout</span>
</li>
<li>
<span class="timeline-when">08:09:31</span>
<span class="timeline-what">Loaded the incremental slice since yesterday 08:00</span>
</li>
<li>
<span class="timeline-when">08:09:30</span>
<span class="timeline-what">Run started by the nightly schedule</span>
</li>
</ul>
Breaks and a foot
.timeline-group is a day, a release or a shift, and is an
<li> in the same list rather than a heading between two, so the record
stays one list. .timeline-more is the foot a record with no end needs; put a
link or a .btn-ghost inside it, because the <li> is a row,
not a control.
- Today
- 09:41 Closed by Priya Nair
- 09:12 Assigned to Fulfilment
- Yesterday
- 18:03 Delivery attempt failed — no answer at the door
- 11:27 Address corrected after a postcode mismatch
- 37 earlier entries, back to 4 March
<ul class="timeline" style="max-width:480px">
<li class="timeline-group">Today</li>
<li data-kind="go">
<span class="timeline-when">09:41</span>
<span class="timeline-what">Closed by Priya Nair</span>
</li>
<li>
<span class="timeline-when">09:12</span>
<span class="timeline-what">Assigned to Fulfilment</span>
</li>
<li class="timeline-group">Yesterday</li>
<li data-kind="danger">
<span class="timeline-when">18:03</span>
<span class="timeline-what">Delivery attempt failed — no answer at the door</span>
</li>
<li data-kind="warn">
<span class="timeline-when">11:27</span>
<span class="timeline-what">Address corrected after a postcode mismatch</span>
</li>
<li class="timeline-more">37 earlier entries, back to 4 March</li>
</ul>
Avatar markers
.timeline-mark--bare drops the tile's chrome and hands the avatar the marker's
diameter, so the two cannot disagree about size. Do not put a data-kind on a
bare entry — the fill is painted over by whatever is in the marker;
.avatar--muted is the honest marker for an actor that is not a person.
- Priya Nair · 10:04 The quota was raised to 8 GB, so this should not recur.
-
Jordan Weiss · 09:52
Archived everything older than two years and asked for a bigger quota.
Freed 1.1 GB. That buys about a fortnight at the current rate, which is why the quota request is the actual fix.
- Housekeeper · 09:31 Opened from an e-mail to the service desk.
<ul class="timeline" style="max-width:520px">
<li>
<span class="timeline-mark timeline-mark--bare"><span class="avatar">PN</span></span>
<span class="timeline-when">Priya Nair · 10:04</span>
<span class="timeline-what">The quota was raised to 8 GB, so this should not recur.</span>
</li>
<li>
<span class="timeline-mark timeline-mark--bare"><span class="avatar">JW</span></span>
<span class="timeline-when">Jordan Weiss · 09:52</span>
<span class="timeline-what">Archived everything older than two years and asked for a bigger quota.</span>
<div class="timeline-body">
Freed 1.1 GB. That buys about a fortnight at the current rate, which is why the
quota request is the actual fix.
</div>
</li>
<li>
<span class="timeline-mark timeline-mark--bare">
<span class="avatar avatar--muted"><i class="ri-robot-2-line" aria-hidden="true"></i></span>
</span>
<span class="timeline-when">Housekeeper · 09:31</span>
<span class="timeline-what">Opened from an e-mail to the service desk.</span>
</li>
</ul>
Sizing it
Five variables size the component, and every offset is derived from them:
--timeline-line (the connector's width), --timeline-dot and
--timeline-marker (the two marker diameters), --timeline-lead
(the height of the row a marker centres in) and --timeline-gap (the space
under an entry). Keep a diameter odd against a 1px line, or the marker
centres on a half pixel and the circle antialiases into a smudge.
- 03:15:02 deploy.web — succeeded in 41s
- 03:14:21 migrate.db — 3 migrations applied
- 03:14:18 migrate.db — advisory lock held, waited 2.4s
- 03:13:55 build.image — pushed sha-9f2c1ab
- 03:12:40 pipeline started from main
<div style="max-width:460px; padding:16px; border-radius:6px; background:var(--surface-raised-2)">
<ul class="timeline" style="--timeline-gap: 6px; --timeline-surface: var(--surface-raised-2)">
<li data-kind="go">
<span class="timeline-when">03:15:02</span>
<span class="timeline-what">deploy.web — succeeded in 41s</span>
</li>
<li data-kind="go">
<span class="timeline-when">03:14:21</span>
<span class="timeline-what">migrate.db — 3 migrations applied</span>
</li>
<li data-kind="warn">
<span class="timeline-when">03:14:18</span>
<span class="timeline-what">migrate.db — advisory lock held, waited 2.4s</span>
</li>
<li data-kind="go">
<span class="timeline-when">03:13:55</span>
<span class="timeline-what">build.image — pushed sha-9f2c1ab</span>
</li>
<li>
<span class="timeline-when">03:12:40</span>
<span class="timeline-what">pipeline started from main</span>
</li>
</ul>
</div>