Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Compact List Timeline Block for React Aria

One timesheet day as six tracked spans on a vertical rail with dashed rows for the untracked minutes between them, totals for tracked and untracked time, and a Fill button that turns a gap into a span with Undo.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Show where a day went, and where it did not, with this compact timeline for React with React Aria. It is written for Spanwise, a fictional time tracker. A hairline frame with a plus mark on each corner holds two figures, the time tracked and the time untracked, and under them a vertical rail of one timesheet day. Each tracked span is a solid dot with its title, its project and its start and end, and its length at the end of the row. Where two spans do not touch, a dashed row says how long nothing was tracked ("35 min untracked") and shows the times it covers, all worked out from the end of one span and the start of the next. A ghost Button, "Fill", turns a gap into a span called "Untracked" in the same row: the figures move from untracked to tracked, the dashed row becomes a solid one without changing height, focus stays on that row, and a polite status line says it was added in this demo and nothing was saved, with an Undo button beside it. It is built from Button and Guides, with an entrance only. For timesheets, shift records and any day that should add up. The spans are example data.

FAQ

How do I change the day?

Edit SPANS at the top of the file, oldest first. Each span has an id, a title, a project and an ISO start and end in UTC. The gaps, the totals and each length are derived by rowsOf() from those times, so a span that starts where the last one ended simply has no gap row. Times that overlap are not handled: keep the spans in order and apart.

What does Fill do?

It adds the gap's id to a list of filled gaps, and rowsOf() then returns a span for it, from the end of one span to the start of the next, called "Untracked". The totals follow. Nothing is saved or sent: the status line says so. To make it real, open your own form or call your API in fill(), and add the returned span to the list instead.

How does Undo work, and where does focus go?

Undo removes the last gap that was filled, so the dashed row returns, and the status line says so. It is always in the foot but hidden and out of the tab order until there is something to undo, so the foot never changes size. After Fill or Undo, focus moves to the row that changed so a keyboard user keeps their place.

Are the times in my time zone?

They are in UTC, as the header says, and written from the ISO strings with no time-zone library, so the page reads the same on the server and in every browser. Convert before you pass spans in if your tracker stores local times.

Is it accessible?

The rail is an ordered list with a heading per span, and each time is a time element. The dashed rows say "untracked" in words, and each Fill button is named with the length and the times it fills, because several rows share the same visible word. The status line is a polite live region that is there from load.

How does it look on a phone?

The rows are the same, just narrower: titles and project lines are cut short with an ellipsis and the length stays at the end. The two figures sit side by side and the status line reserves three lines so the foot does not move.