shadcn.io is not affiliated with official shadcn/ui
Profile Header Skeleton Block for Radix UI
A centred musician's profile header that loads in place: an avatar, a name, a two-line bio and Followers, Following and Posts figures come in over bars, with a Follow toggle that adds one to the count.
Installation
Overview
Show a profile header while it loads, then swap the person and the figures in without moving anything, with this profile header skeleton for React with Radix UI. A hairline frame with a plus mark on each corner holds a centred header from Obbligato, a musicians' network, in three bands: the person, three figures and the actions. The card, the three figure labels (Followers, Following, Posts), the Follow Toggle and the Message button are real from the first paint; the large initials Avatar, the name, the two-line bio and the three numbers are Skeleton bars that become the profile in the same place, the bio coming in a word at a time through a Skeleton Text effect. Follow is a pressed toggle that swaps its label at a reserved width and moves the follower count by one; Message says it is not wired in this demo. For artist pages, member headers and any profile that leads with a person and their numbers.
FAQ
What stays local, and how do I wire it to a real query?
Nothing is fetched. The profile is typed in PROFILE. 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 card from your query's pending state, or use the bars as a Suspense fallback and render the real content when it arrives. Keep one component for the bars and the data so they can't drift apart.
How does the Follow toggle work?
It is a pressed toggle with a fixed width: "Follow" and "Following" share one grid cell, so the button never resizes, and the sr-only name stays "Follow Ludmila Overbeck" with aria-pressed carrying the state. Pressing it adds one to the follower count, which is derived from the typed number plus the toggle, and the polite line says the choice is kept in this tab only. Load and Reload clear it. In your app, send the request from the press handler and set the pressed state from the response.
Why doesn't the header jump when the data arrives?
Every value shares a grid cell with its bar, and the bio's bars and words share one cell inside the catalog Skeleton Text effect, so the taller of the two holds the height on every phone width. The figures are fixed-size cells with tabular numbers, the two buttons are real in both phases, and the polite line reserves two lines.
How are the numbers formatted?
Through one Intl.NumberFormat("en-US") at module scope, wrapped in bdi so a right-to-left page keeps the digits in order. The format is the same on the server and in the browser, so hydration finds no difference. Swap the locale there, or print the counts as you receive them.
Is it accessible?
The card is marked busy while it loads and carries a screen-reader-only "Loading profile…", 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 Follow toggle is aria-disabled while loading rather than disabled, so it keeps its place in the tab order. 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.