Timeline
History in order: the progress of an order, what happened to a contract, the versions of a
changelog. It's just classes on <ol> — the order is part of the meaning, and the screen reader announces
"item 2 of 5" without anyone writing ARIA. The dot and the rail are drawn by the class; the template only brings the
content.
-
Order confirmed
Payment approved via Pix. -
Picked at the warehouse
-
In transit
Left the Campinas distribution center. -
Out for delivery
-
Delivered
Expected: 12/09/2026.
What already happened has a filled dot in the success tone; the current step, filled in the accent; what's still ahead stays hollow and neutral. The dot tells the state — the text is free to say what and when.
Examples
Everything below is the same list, with tone, fill, icon and badge swapped.
Contract history, with icons
tuc-timeline__icon as the item's first child puts the icon in place of the dot.
-
Contract signed
Both parties signed through the portal. -
Sent for signature
-
Clause 4 revised
Late fee went from 2% to 1%. -
Draft created
Audit log, with badge
A tuc-badge in the header aligns to the center of the row.
-
Permission removed
AccessAna Lima removed Rafael Souza's access to finance. -
Sign-in from a new device
SessionChrome on macOS, São Paulo. -
Report exported
Data
The five tones, hollow
Without is-filled the dot is a ring in the tone, over the background.
is-accent
is-success
is-warning
is-danger
is-info
no tone
The five tones, filled
is-filled fills the dot with the item's tone.
is-accent is-filled
is-success is-filled
is-warning is-filled
is-danger is-filled
is-info is-filled
is-filled, no tone
How to use
There's no JavaScript and no data-* attribute: write the classes and you're done. Each item has a
header with title and time, and an optional body.
<ol class="tuc-timeline">
<li class="tuc-timeline__item is-success is-filled">
<div class="tuc-timeline__head">
<p class="tuc-timeline__title">Contract signed</p>
<time class="tuc-timeline__time" datetime="2026-09-12">12/09/2026</time>
</div>
<div class="tuc-timeline__body">Both parties signed.</div>
</li>
<li class="tuc-timeline__item">
<div class="tuc-timeline__head">
<p class="tuc-timeline__title">Sent for signature</p>
<time class="tuc-timeline__time" datetime="2026-09-10">10/09/2026</time>
</div>
</li>
</ol>With an icon
The icon goes in a <span> as the item's first child. When it's there, the dot goes away and the circle
gets the tone's soft background; the SVG is sized by the class.
<li class="tuc-timeline__item is-success">
<span class="tuc-timeline__icon"><svg viewBox="0 0 24 24" aria-hidden="true">...</svg></span>
<div class="tuc-timeline__head">...</div>
</li>In the Django template
The tone usually comes from the event type. A dictionary on the model, or a filter, avoids a cascade of if in the
template.
class Event(models.Model):
TONES = {"signed": "is-success", "revised": "is-warning", "canceled": "is-danger"}
kind = models.CharField(max_length=20)
title = models.CharField(max_length=120)
created_at = models.DateTimeField(auto_now_add=True)
@property
def tone(self):
return self.TONES.get(self.kind, "")<ol class="tuc-timeline">
{% for event in contract.events.all %}
<li class="tuc-timeline__item {{ event.tone }}{% if forloop.first %} is-filled{% endif %}">
<div class="tuc-timeline__head">
<p class="tuc-timeline__title">{{ event.title }}</p>
<time class="tuc-timeline__time" datetime="{{ event.created_at|date:'c' }}">{{ event.created_at|date:"d/m/Y H:i" }}</time>
</div>
</li>
{% endfor %}
</ol>Adjusting the geometry
Three variables on the list itself control the math for the dot and the rail. The first line of the header has a fixed
height for the same reason as .tuc-choice: the center of the dot can't depend on the project's font.
.timeline-large {
--tuc-timeline-gutter: 2rem; /* dot column; the icon fills all of it */
--tuc-timeline-dot: 1rem; /* dot diameter */
--tuc-timeline-line: 2rem; /* height of the header's first line */
}Accessibility
The structure does the work. <ol> gives each item's position, <time datetime>
gives the date in a machine-readable format, and the dot and rail are pseudo-elements, invisible to the screen reader.
Color can't be the only information
The dot's tone doesn't reach people who can't see it and confuses people who can't tell colors apart. State it in the text — "Canceled", "In transit" — or in a tuc-badge in the header, and mark the icon's SVG with aria-hidden="true".
Classes
Classes only, no JavaScript component.
| Class | Where | What it does |
|---|---|---|
tuc-timeline | <ol> | The list; removes marker and indentation |
tuc-timeline__item | <li> | An event; draws dot and rail. The last item has no rail |
is-accent is-success is-warning is-danger is-info | item | Tone of the dot and icon; without a tone, --tuc-subtle |
is-filled | item | Filled dot in the tone; without it, hollow |
tuc-timeline__icon | item's first child | Icon in place of the dot, with the tone's soft background |
tuc-timeline__head | item | Row for the title, the time and a badge; wraps when it doesn't fit |
tuc-timeline__title | in the header | Title in weight 600, no margin — can be a <p> or an <h2>/<h3> |
tuc-timeline__time | in the header | Date or time, smaller, in a muted tone and with fixed-width digits |
tuc-timeline__body | item | Supporting text in a muted tone |
| Variable | Default | Controls |
|---|---|---|
--tuc-timeline-gutter | 1.5rem | Width of the dot column and size of the icon |
--tuc-timeline-dot | 0.75rem | Dot diameter |
--tuc-timeline-line | 1.5rem | Height of the header's first line, where the dot is centered |