Timeline

Events in order along a vertical line, with a dot per event that can carry its own color or glyph.

Accessibility

What Timeline does for you, and what it leaves to the page around it.

What it renders

The root is an <ol> and each item is an <li>, so you do not have to announce the sequence. A screen reader already says “list, 7 items” and counts the rows as it goes, and an ordered list says the order matters.
  • There is no tab stop. Timeline is content, not a control, so a keyboard reader moves through it with reading keys, not with Tab.
  • That makes any control inside a row the only tab stop. Nothing inside content is styled for you, so a button there is a plain button. Its label has to make sense read on its own: “Remind” on row four of a list is not “Remind Dana about the staging approval”.
  • A heading inside a row is a heading on the page. If the rows carry them, keep them at one level and below whatever heading introduces the timeline, so the document outline still describes the page.
  • The list itself has no name. Where the surrounding heading does not already say what is being listed, put one above it instead of labeling the <ol>.

Color is never alone

color is the easiest item prop to lean on too hard. A red dot is not read out, is lost on anyone who cannot tell it from the green one, and disappears in a high contrast mode.
  • Say it in content. “Failed: timed out after 30s” carries the same thing the red dot does, to everybody, and it is what a search over the log will match.
  • An icon that differs in shape as well as color helps sighted readers, as a second signal beside the words.
  • loading: true changes the row's styling, not its text. A row that is still running should say so in its content as well, since the styling is all a reader who cannot see it misses.
  • Reserve one meaning per color across the product. A reader who has learned that red is a failure should never meet a red dot that means something else.

Reading order against visual order

The page order is one straight sequence in every mode, and the layout is done with CSS on top of it. That is usually what you want, with one mode to watch.
  • alternate is a single sequence read out as one column. Each row goes to a side by its index, even rows at start and odd rows at end, through CSS only. A screen reader gets rows 1, 2, 3 in order, with no sign that 1 and 3 were on the left. If the two sides mean two things, the row has to say which it is.
  • A per-item placement does not move the row. It changes which side it paints on and leaves its position in the list alone, so a timeline that reads correctly by eye can be telling a different story in sequence.
  • reverse is the safe one. It reverses the items before rendering, so the page order and the visual order still agree. Reversing with CSS instead would split them.
  • start and end are logical, so the whole layout mirrors under dir="rtl" with nothing else to change. Replace the deprecated left and right with them.

Naming, or hiding, a custom dot

The default dot carries nothing. A custom icon is your own node, so whether it says anything is up to you, and it usually should say nothing.
  • Decorative is the normal case. If the row's text already says “Deployed to production”, the rocket beside it is duplication. Give the glyph aria-hidden="true" and let the sentence do the work.
  • A glyph that is the only thing saying something needs a name, which usually means the sentence is missing a word. Fix the sentence first. aria-label on an icon is the fallback.
  • A spinner is not a status message. The rotating glyph on a loading row announces nothing. Where the running state matters to a reader who is waiting on it, the change belongs in a live region you own, not in the dot.
  • Never put a focusable element in icon. The dot is a decoration on a list marker, not a control, and a tab stop there lands somewhere with nothing visible to press.

Contrast and motion

  • The connector line is structure, not decoration. It is what says the rows belong to one sequence, so it has to stay visible. It is a non-text element, so it needs 3:1 against the surface. A line thinned or faded for looks is the first thing to go in a bright room or on a dimmed screen.
  • A custom color is not checked for you. A color other than the presets is used as written, with nothing checking it against the background. A color picked for light mode can vanish in dark mode, so use a theme variable like var(--ant-magenta-6) instead of a hex.
  • Gutter text is still text. A timestamp in title in a muted color at a small size is where this component usually drops below 4.5:1.
  • Any spin is yours to gate. An icon from your pack does not spin by itself, so the rotation comes from a class you wrote. Put it behind motion-safe:. Turning motion off in the theme does not reach a class you added.