shadcn.io is not affiliated with official shadcn/ui
Comparison Table Skeleton Block for Radix UI
A plans-by-features matrix that loads in place: the eight feature names stay while three plan heads, every check or value and the Choose buttons come in column by column.
Installation
Overview
Show a plan comparison while its data loads, then swap it in column by column, with this comparison table skeleton for React with Radix UI. A hairline frame with a plus mark on each corner holds a band heading and a native table of eight features by three plans from Stringer, a scheduling service. The feature column is real from the first paint, since it is the page's own text; the three plan heads (name, price, billing note), every cell and the three Choose buttons are Skeleton bars that become a check, a dash or a typed value in the same cell. A check has a hidden "Included" and a dash a hidden "Not included", so the matrix reads without the icons. The table keeps its width inside a named scroller with the feature column pinned to the start, so a phone pans the plans beneath it. It's built from Skeleton, Button and Guides, with a CSS entrance only. For plan pages and any matrix with fixed row labels.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The plans and features are typed in PLANS and FEATURES, 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 content when it arrives. Choose only sets a note here; in your app, start checkout for the chosen plan from it. Keep one component for the bars and the data so they can't drift apart.
Why are the feature names real while the plan heads are bars?
The features are what the page is about and don't depend on the plan data, so a reader can start scanning them at once; the plan names, prices and every cell do depend on it. If your features also come from a CMS, make label a bar the same way.
How does a cell say yes or no?
A value of yes draws a check with a hidden "Included", no draws a dash with a hidden "Not included", and anything else is shown as typed ("10", "Unlimited", "Within 4 hours"). Meaning is never in the icon alone, and nothing in the table is coloured.
How does it look on a phone?
The table has a minimum width and sits in a scrollable region named "Stringer plans compared", which a keyboard can focus and scroll. The feature column is pinned to the start with a solid background, so it stays in view while the plans pan under it, and the pin follows the reading direction.
Is it accessible?
The region is marked busy while it loads and carries a screen-reader-only "Loading plans…", 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. Heads are th scope="col" and feature names th scope="row", with a hidden caption. 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.