Accessibility pattern · Disclosure and content

Timeline

An ordered list is the timeline: screen readers say how many steps there are, and each step's time is a time element with a machine-readable datetime. Each status is a word and an icon of its own shape, never only a color, and the step in progress carries aria-current="step".

WCAG criteria
5
Keyboard rules
2
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Order AA-48213

Blue pottery vase, ₹2,450

Arriving today by 7 pm

  1. Reached the Pune hub

    Done

    Sorted for delivery in Kothrud.

  2. Out for delivery

    In progress

    Rahul is bringing it and will call before he arrives.

  3. Delivered

    Expected

    Someone at the address can sign for it.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabReaches the earlier events button; the list has no controls of its own, so the next Tab leaves the timeline.
Enter or SpaceShows or hides the three earlier events at the top of the list; focus stays on the button.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the button3 earlier events, button, collapsed
Enter shows themExpanded
Reading into the listDelivery history, list, 6 items
Reading the step in progressCurrent step. Out for delivery. In progress, Today, 9:20 am. Rahul is bringing it…
Reading a finished stepReached the Pune hub. Done, 4 Oct, 10:15 pm. Sorted for delivery in Kothrud.

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-timeline" data-ap-timeline>
  <div class="ap-timeline__top">
    <div>
      <h3 class="ap-timeline__order">Order AA-48213</h3>
      <p class="ap-timeline__item">Blue pottery vase, ₹2,450</p>
    </div>
    <p class="ap-timeline__eta">Arriving today by 7 pm</p>
  </div>

  <button type="button" class="ap-timeline__earlier" aria-expanded="false" aria-controls="tl-placed tl-packed tl-shipped">
    <svg class="ap-timeline__chev" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"/></svg>
    <span>3 earlier events</span>
  </button>

  <ol class="ap-timeline__list" role="list" aria-label="Delivery history">
    <li class="ap-timeline__step ap-timeline__step--done ap-timeline__step--earlier" id="tl-placed" hidden="until-found">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6.5 12.5 3.5 3.5 7.5-8"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Order placed</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">Done</span> <time datetime="2026-10-01T11:02+05:30">1 Oct, 11:02 am</time></p>
          <p class="ap-timeline__note">Paid by UPI. A receipt is in your email.</p>
        </div>
      </div>
    </li>
    <li class="ap-timeline__step ap-timeline__step--done ap-timeline__step--earlier" id="tl-packed" hidden="until-found">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6.5 12.5 3.5 3.5 7.5-8"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Packed in Jaipur</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">Done</span> <time datetime="2026-10-02T16:30+05:30">2 Oct, 4:30 pm</time></p>
          <p class="ap-timeline__note">Wrapped in recycled paper padding.</p>
        </div>
      </div>
    </li>
    <li class="ap-timeline__step ap-timeline__step--done ap-timeline__step--earlier" id="tl-shipped" hidden="until-found">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6.5 12.5 3.5 3.5 7.5-8"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Shipped</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">Done</span> <time datetime="2026-10-03T18:40+05:30">3 Oct, 6:40 pm</time></p>
          <p class="ap-timeline__note">Handed to the courier. Tracking number 7741 2290 553.</p>
        </div>
      </div>
    </li>
    <li class="ap-timeline__step ap-timeline__step--done">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6.5 12.5 3.5 3.5 7.5-8"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Reached the Pune hub</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">Done</span> <time datetime="2026-10-04T22:15+05:30">4 Oct, 10:15 pm</time></p>
          <p class="ap-timeline__note">Sorted for delivery in Kothrud.</p>
        </div>
      </div>
    </li>
    <li class="ap-timeline__step ap-timeline__step--now" aria-current="step">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4.5"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Out for delivery</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">In progress</span> <time datetime="2026-10-05T09:20+05:30">Today, 9:20 am</time></p>
          <p class="ap-timeline__note">Rahul is bringing it and will call before he arrives.</p>
        </div>
      </div>
    </li>
    <li class="ap-timeline__step ap-timeline__step--next">
      <div class="ap-timeline__row">
        <span class="ap-timeline__mark"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="6.5"/><path d="M12 8.5V12l2.5 1.5"/></svg></span>
        <div class="ap-timeline__body">
          <p class="ap-timeline__what">Delivered</p>
          <p class="ap-timeline__meta"><span class="ap-timeline__state">Expected</span> <time datetime="2026-10-05T19:00+05:30">Today, by 7 pm</time></p>
          <p class="ap-timeline__note">Someone at the address can sign for it.</p>
        </div>
      </div>
    </li>
  </ol>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    Steps are items of an ordered list, times are time elements with a datetime, and the current step is marked in the markup, not just drawn.

  • 1.3.2 Meaningful Sequence Level A

    The list runs oldest to newest on screen and in the source, and the earlier events appear straight after the button that reveals them.

  • 1.4.1 Use of Color Level A

    Every status is a word (Done, In progress, Expected) and an icon of its own shape; color only repeats it.

  • 1.4.11 Non-text Contrast Level AA

    Step markers and the line between them clear 3:1 against the surface in both themes.

  • 4.1.2 Name, Role, Value Level A

    The button exposes aria-expanded, and aria-current="step" marks the step in progress.

Usage

When to use it

Use it

  • Something that moves through known steps over time: an order, an application, a support ticket.
  • A history people check for the latest event first, with older ones kept close by.

Use something else

  • Steps the person completes themselves in a form: use progress steps.
  • An open-ended stream of updates: use a feed.
  • Two or three events: a sentence ("Shipped on 3 October") says it faster.

Common failures

How it usually goes wrong

  • A timeline of divs

    Without a list, a screen reader cannot say how many steps there are or where one ends. An ordered list says both.

  • Status shown only by color

    A green dot and a grey dot look the same to many people. Each step here says Done, In progress or Expected, beside an icon of a different shape.

  • The current step not marked

    A highlighted box means nothing to a screen reader. aria-current="step" makes the step announce itself as current.

  • Dates only people can read

    "Today" and "4 Oct" cannot be parsed by software. The time element's datetime carries the exact moment and the time zone.

  • History removed instead of collapsed

    Sending older events to another page loses context. A disclosure keeps them one key press away, and find-in-page still reaches them.

  • Icons that speak

    An unlabelled SVG can be read as "image" or not at all, unpredictably. The icons here are hidden; the words carry the status.

Notes

Building it

  • Put aria-current="step" on the list item of the step in progress and move it as the order moves; only one step is current.
  • aria-controls lists all three earlier items, because they live in the same list rather than in one panel.
  • The earlier items use hidden="until-found": a find-in-page match in one of them opens all three and updates the button.
  • list-style: none removes list semantics in Safari; role="list" on the ol puts them back.
  • Write relative words (Today) for people and keep the absolute moment, with its offset (+05:30), in datetime.

Sources: HTML: the time element · WAI-ARIA 1.2: aria-current

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction