shadcn.io is not affiliated with official shadcn/ui
Progress Bar Skeleton Block for React Aria
Quarter goals and a milestone rail that load in place: headings and the rail's line stay while goal counts, progress bars that fill from zero and five dated milestone nodes become data.
Installation
Overview
Show goal progress while it loads, then fill the bars in without moving anything, with this progress bar skeleton for React with React Aria. A hairline frame with a plus mark on each corner holds two bands from Cantabile, an OKR tracker. The goals band is three rows, each a label and a count over a Progress bar; the milestones band is a five-node rail from Kickoff to Launch with a done, an in-progress and a next state. The headings and the rail's line are real from the first paint; the labels, the counts, the bars, the nodes and their names and dates are Skeleton bars that become the data in the same box, row by row, and each progress bar fills from zero to its share when the data is in. A milestone's state is a check, a dot or a dashed ring and a word. On a phone the rail keeps its width in a named scroller. It's built from Skeleton, Progress, Button and Guides, with a CSS entrance only. For goal trackers, onboarding checklists and any list of things that are partly done.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The three goals and the five milestones are typed in GOALS and MILESTONES, 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 figures when they arrive. The bars are derived from the same data as the figures, so keep one component for both. Give each bar the share your API returns.
How does the bar fill?
The Progress component is given 0 while the data is loading and its share once it is ready, and its indicator transitions between them over 700 milliseconds, each row a little after the one above. The bar is dir="ltr", so it fills from the same side in every reading direction. Under reduced motion it jumps to its value.
Does the block keep its height 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. The Progress track is a fixed 8 pixels tall in both phases, and each node, name and date has its box. The labels, headings and frames 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.
Is it accessible?
The region is marked busy while it loads and carries a screen-reader-only "Loading the goals…", 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. Each count is plain text ("3,640 of 5,000, 73%"), so the bar itself is hidden from assistive technology, and a milestone's state is a word, not just a glyph. The pulse stops when the data is in, and under reduced motion there is no pulse, no spinner, no stagger and no growing: the swap is instant.