shadcn.io is not affiliated with official shadcn/ui
Timeline Skeleton Block for React Aria
A release history on a vertical rail that loads in place: the rail stays while five numbered markers, dates, version badges, titles and notes fill in entry by entry.
Installation
Overview
Show a history while it loads, then swap the entries in without moving anything, with this timeline skeleton for React with React Aria. A hairline frame with a plus mark on each corner holds a band with the heading "Releases" and the count, over an ordered list of five releases on a vertical rail for Baluster, a changelog service. The heading and the rail are real from the first paint; each entry's numbered marker, date, version badge, title and two lines of notes are Skeleton bars that become the data in the same cell, entry by entry, so each marker fills as its row arrives, when you press Load. Reload goes back to the bars. The control is an outline Button, the bands are ruled by Guides, and a polite line says what loaded. It's built from Skeleton, Badge, Button and Guides, with a CSS entrance only. For changelogs, histories and any rail of dated entries that waits for a query.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The five releases are typed in RELEASES, Load starts a 700 millisecond timer, and when it ends the phase becomes ready and the data swaps in; Reload goes back to the bars and in again. To wire it for real, delete the timer, the Load button and the polite line, and set data-state (loading or ready) and aria-busy on the region from your query's pending state, or use the bars as a Suspense fallback and render the real entries when they arrive. The bars are derived from the same array as the data, so keep one component for both.
Does the rail keep its length while it loads?
Each bar and its data sit in one grid cell, so the larger of the two sets the cell's box and the swap changes nothing around it. Every entry has a fixed height, the rail is drawn by the list itself rather than by the entries, and the marker, date, badge, title and each note line share a cell with their bar. The heads, labels and frame are real in both phases, and the polite line reserves two lines, so the block is exactly as tall before, during and after loading at every width.
Where do the dates come from?
From typed ISO strings (2026-10-09), shown through a small month table as "9 Oct 2026" and kept as the dateTime of a real time element. Nothing reads the clock, so the same date shows on every machine. In your app, format your own dates with Intl.DateTimeFormat after mount.
Why are the markers numbered?
The newest release has the highest number, which gives each entry a name that doesn't depend on color or position: the list is labelled "Releases, newest first" and the number is part of its marker. Drop the number and keep a plain dot if your entries have no order.
Is it accessible?
The region is marked busy while it loads and carries a screen-reader-only "Loading releases…", and the polite line announces what loaded once. The bars are hidden from assistive technology and the layer that isn't showing is invisible, so it is neither read nor focusable. The pulse stops when the data is in, and under reduced motion there is no pulse, no spinner and no stagger: the swap is instant. The entries are an ordered list, and the rail is a decorative line drawn by CSS.