Building the vertical line, icon badges and responsive behavior with Tailwind
Whether it is an order history, an audit log, or a project's activity feed, chronologically ordered events are one of the most common recurring UI patterns in web applications. Visually there is more to them than it first appears, from the vertical connecting line between events, through icon badges per event type, to handling description text of wildly varying length without breaking the grid. This article shows how to structure a timeline component cleanly with plain Tailwind CSS and where a compact activity list diverges from a detailed timeline view.
Table of Contents
- 1. Timeline and activity feed: related but distinct patterns
- 2. Building the vertical line with dots
- 3. Icon badges per event type
- 4. Responsive behavior with long description text
- 5. Compact activity list versus detailed timeline
- 6. Timestamps: relative time versus an absolute date
- 7. Grouping by day with dedicated date headers
- 8. Status-based color coding across the whole timeline
- 9. Accessibility: semantics, not just visuals
- 10. Summary
- 11. FAQ
1. Timeline and activity feed: related but distinct patterns
Both components display a sequence of events along a time axis, but they differ in density and purpose. A timeline emphasizes the narrative flow, for instance an order's status from placement through delivery, and expects relatively few, content-rich events. An activity feed, by contrast, logs many small events in a short span, for example every comment or status change in a project tool, and needs to stay far more compact and quickly scannable than a classic timeline.
For the Tailwind styling, that implies two different starting points: a timeline works with generous whitespace, clearly visible milestone dots, and often accompanying body text or even images per event. An activity feed, on the other hand, reduces every entry to a single or double line core statement with a timestamp, so twenty or more entries remain skimmable at a glance. Both variants still share the same structural skeleton of a vertical line, dots and a content block, which is built out in the next section.
2. Building the vertical line with dots
The structural core element of any timeline is a continuous vertical line that strings together the individual event dots. With Tailwind this can be implemented without pseudo-elements using a simple flexbox layout per row: a fixed-width left column holds the dot and line, and a right column holds the content. The line itself is a narrow div with w-px bg-slate-200, stretched across the full height of the left column through absolute positioning, while the dot sits above it as a small rounded circle with rounded-full.
For the line to read as continuous rather than interrupted at every event, it needs to run across the entire height of its parent, including through the area where the next dot sits, simply getting visually covered by it. On the last entry in the list, the line should end before the dot begins, otherwise a visually confusing line fragment dangles below the final event. The simplest fix is a Tailwind equivalent of a :last-child selector, using the last:hidden group utility on a separate line element per row.
<ol class="relative">
<li class="relative flex gap-4 pb-8 last:pb-0">
<!-- Left column: dot + line -->
<div class="relative flex w-8 flex-none flex-col items-center">
<span class="z-10 flex h-8 w-8 items-center justify-center rounded-full
bg-sky-500 text-white ring-4 ring-white dark:ring-slate-900">
<!-- Icon here -->
</span>
<span class="absolute top-8 bottom-0 w-px bg-slate-200
dark:bg-slate-700 last:hidden"></span>
</div>
<!-- Right column: content -->
<div class="flex-1 pt-1">
<p class="text-sm font-medium text-slate-900 dark:text-slate-100">
Order was shipped
</p>
<time class="text-xs text-slate-400">2 hours ago</time>
</div>
</li>
</ol>
3. Icon badges per event type
The dot on the timeline's line is the ideal spot to visually encode the event type rather than describing it purely in text. A generously sized circle of roughly 32 pixels with a small icon in the middle and a type-specific background color is usually enough to identify the event type at a glance, for instance a checkmark for completed steps, a clock for pending ones, and a warning triangle for failed events. A white or colored ring around each dot, typically ring-4 ring-white, matters here since it visually separates the dot from the line underneath, which would otherwise blend seamlessly into it.
The badge color palette should stick to a fixed, small set, for example green for successful, yellow or amber for pending, red for failed, and a neutral blue or gray for informational events. This color coding must never be the only source of information: the icon itself and accompanying text need to carry the same information, so users with color vision deficiencies can still correctly identify the event type. With a large number of distinct event types, a central mapping table in the code that defines type, icon and color in one place is worth the effort, rather than repeating that mapping throughout the template.
4. Responsive behavior with long description text
Once an event carries more than a short status line, for instance a full comment in an activity feed, the layout can no longer rely on a fixed line height per entry. The content block to the right of the line needs a flexible height, while the left column with dot and line has to independently track the actual height of each entry, otherwise entries of varying length produce a visually messy line with uneven spacing between the dots.
On narrow screens, the available horizontal space for text also gets tight, especially when an avatar image or a timestamp shares the same row. Applying Tailwind's flex-wrap to an entry's header area lets name, action and timestamp wrap onto two lines automatically on narrow screens, instead of getting clipped by a hard overflow-hidden. Long free-form text should generally carry break-words, so a single long word, such as a URL inside a comment, cannot push the layout past its available width.
5. Compact activity list versus detailed timeline
A compact activity list reduces every entry to the bare minimum: a small dot with no dedicated icon, a single-line core statement, and a relative timestamp on the right edge. Vertical spacing between entries stays correspondingly tight, usually no more than py-2, so as many events as possible stay visible without scrolling. This variant suits contexts where users want a fast overview of many events, for example inside a sidebar or a dashboard widget.
A detailed timeline view, on the other hand, deliberately invests more space per event: larger icon badges, a more prominent event title, an optional description, and sometimes even accompanying images or file attachments. Vertical spacing between events is chosen more generously here, often pb-8 or more, so each event reads as a self-contained, clearly delimited block. This variant fits contexts where the history itself is the central piece of information, for instance an order history or a project's progress, something a user is meant to follow closely rather than just skim.
6. Timestamps: relative time versus an absolute date
Timestamps in timelines are usually shown as relative time, such as '3 hours ago' or 'yesterday', because that reads faster than an absolute date. It matters to still keep the absolute date accessible, typically through the title attribute of the <time> element or a hover tooltip, so users can look up the exact moment when needed without the compact relative display having to give way for it. Visually the timestamp gets a noticeably muted color compared to the actual event text, usually text-slate-400, since it is relevant but clearly secondary to the actual content.
For longer timelines spanning several days or weeks, a purely relative time quickly loses precision, since '5 days ago' is less concrete than an actual date. A hybrid approach works well here: events from the last 24 hours get a relative label, older events get a fixed date formatted as day and month. This switch should happen consistently in one central spot in the formatting logic, not through duplicated markup with conditional display scattered across the template.
7. Grouping by day with dedicated date headers
Once an activity feed spans multiple days, grouping by calendar day noticeably helps with scanning the list, similar to how chat applications group messages by day. A date header, such as 'Today', 'Yesterday', or a specific date, deliberately breaks the continuous vertical line at that point and gets its own, slightly offset formatting with sticky top-0, so it stays visible while scrolling through the group and the user always knows which day they are currently looking at.
The timeline's line itself reasonably pauses at every day header and starts fresh underneath, rather than visually running straight through the break, since a continuous line across multiple day groups would suggest a single connected sequence even though these are actually separate daily sections. The date header usually gets a subtly offset background, such as bg-slate-50, to clearly separate it from the actual event entries without becoming visually too dominant.
8. Status-based color coding across the whole timeline
Beyond the color of a single icon badge, it is worth coloring the line itself based on status for process-oriented timelines, such as an order history. Completed segments of the line between two already-reached milestones get a bold color like bg-emerald-500, while the segment between the last reached milestone and the next, still-pending one stays gray. That creates the impression of a progress bar built directly into the timeline at a glance, without needing a separate progress element.
It matters not to let the color transition end abruptly at some arbitrary pixel, but to switch the color consistently right at the position of the relevant dot, so the visual boundary between done and pending lines up exactly with the corresponding milestone icon. For a failed or aborted action in the middle of a process, the line should continue in a warning color like bg-red-400 from that point onward, making it immediately clear that the remainder of the flow deviates from the normal case.
9. Accessibility: semantics, not just visuals
A timeline should be marked up semantically as an ordered list with <ol>, since the chronological order of events carries meaning that an unordered list would not convey. Every event icon that is purely decorative next to an already present text label gets aria-hidden="true", so screen readers do not announce it redundantly, while icons that are the sole source of information for the event type require an aria-label with a textual description of the status.
The relative timestamp should always be wrapped in a semantic <time datetime="..."> element carrying the machine-readable ISO date, even when the visible label just reads '2 hours ago', so screen readers and other tools can interpret the exact moment correctly. For interactive timelines where individual events can be expanded, the focus state of the clickable area needs to be clearly visible, usually through a Tailwind focus ring, since otherwise the timeline becomes practically unusable for keyboard-only users.
| Event type | Icon color | Typical use | Line color after |
|---|---|---|---|
| Success | bg-emerald-500 | Completed step, confirmed action | bg-emerald-500 |
| Pending | bg-slate-300 | Milestone not yet reached | bg-slate-200 |
| Warning | bg-amber-500 | Action needs attention but did not fail | bg-amber-400 |
| Failed | bg-red-500 | Aborted or failed action | bg-red-400 |
| Information | bg-sky-500 | Purely informational event, no status change | bg-slate-200 |
Mironsoft
Tailwind CSS architecture, design systems, and performance
Tailwind frontends that stay maintainable despite thousands of utility classes?
We review existing Tailwind projects for bloated class lists, inconsistent design tokens, and unused CSS remnants, then build a design system that scales cleanly instead of getting messier with every component.
Design System Review
Checking tokens, spacing scale, and component consistency for maintainability.
Performance Optimization
Systematically reducing CSS bundle size, purge configuration, and load times.
Component Architecture
Building reusable, well-structured components instead of sprawling class lists.
10. Summary
Timeline and Activity Feed Components with Tailwind: The Essentials at a Glance
Skeleton
Two-column layout of dot-plus-line on the left and a content block on the right, line ends before the last dot.
Icon badges
Color, icon and text carry the same information redundantly, never color alone as the status source.
Compact vs. detailed
Activity list minimizes spacing and content per entry, timeline deliberately invests more space per event.
Accessibility
Ordered list, aria-label for icon-only information, time element with a machine-readable date.