Data display
Skeleton
Skeletons hold the shape of content while it loads, so the layout doesn't jump when data arrives. Use the Skeleton block to sketch any layout, or the presets for text, avatars, cards and tables. A slow, subtle shimmer shows that something is happening and stops for people who prefer reduced motion.
Loading dashboard
<SkeletonGroup label="Loading dashboard" className="flex flex-col gap-5 rounded-ui-lg border border-line bg-surface p-5"> <div className="flex items-center justify-between gap-4"> <div className="flex items-center gap-3"> <Skeleton shape="circle" className="size-10" /> <div className="flex flex-col gap-2"> <Skeleton shape="text" className="w-36" /> <Skeleton shape="text" className="h-2.5 w-24" /> </div> </div> <Skeleton className="h-8 w-24" /> </div> <div className="grid grid-cols-3 gap-3"> <Skeleton className="h-16" /> <Skeleton className="h-16" /> <Skeleton className="h-16" /> </div> <Skeleton className="h-32 w-full" /></SkeletonGroup>Usage
components/ui/skeleton.tsx
import { Skeleton, SkeletonGroup, SkeletonText } from "@/components/ui/skeleton"; <SkeletonGroup label="Loading profile"> <Skeleton shape="circle" className="size-10" /> <SkeletonText lines={2} /></SkeletonGroup>Examples
Overview
Loading dashboard
<SkeletonGroup label="Loading dashboard" className="flex flex-col gap-5 rounded-ui-lg border border-line bg-surface p-5"> <div className="flex items-center justify-between gap-4"> <div className="flex items-center gap-3"> <Skeleton shape="circle" className="size-10" /> <div className="flex flex-col gap-2"> <Skeleton shape="text" className="w-36" /> <Skeleton shape="text" className="h-2.5 w-24" /> </div> </div> <Skeleton className="h-8 w-24" /> </div> <div className="grid grid-cols-3 gap-3"> <Skeleton className="h-16" /> <Skeleton className="h-16" /> <Skeleton className="h-16" /> </div> <Skeleton className="h-32 w-full" /></SkeletonGroup>Text
SkeletonText draws lines of slightly different widths, with a shorter last line like real text.
Loading
Loading
<SkeletonText lines={1} /><SkeletonText lines={4} />Avatar
A round placeholder with one or two lines, for comments, members and notifications.
Loading
Loading
Loading
<SkeletonAvatar size="lg" /><SkeletonAvatar size="md" /><SkeletonAvatar size="sm" lines={1} />Card
Matches the Card layout, with or without media on top.
Loading
Loading
<SkeletonCard /><SkeletonCard media={false} />Table
A header row and body rows with varied widths; the last column is right-aligned like amounts.
Loading invoices
<SkeletonTable rows={5} columns={4} label="Loading invoices" />Loading to content
Keep the placeholder the same size as the content it stands in for, so nothing shifts on swap.
Loading
Loading
<div className="flex flex-col gap-4 rounded-ui-lg border border-line bg-surface p-5" aria-busy={loading}> {loading ? comments.map((c) => <SkeletonAvatar key={c.name} lines={2} />) : comments.map((c) => ( <div key={c.name} className="flex items-center gap-3"> <Avatar name={c.name} decorative /> <div className="flex min-w-0 flex-col"> <span className="text-[13px] font-medium">{c.name}</span> <span className="text-[13px] text-muted">{c.text}</span> </div> </div> ))}</div>Props
Skeleton
PropTypeDefaultDescription
shape"rect" | "text" | "circle"default: "rect"rect follows the theme radius, text is a 12 px line, circle is round.classNamestringdefault: —Size it with width and height classes.styleCSSPropertiesdefault: —Inline size when it is computed.
SkeletonGroup
PropTypeDefaultDescription
labelstringdefault: "Loading"Announced once for the whole group.classNamestringdefault: —Layout classes.
Presets
PropTypeDefaultDescription
SkeletonText linesnumberdefault: 3Number of lines.SkeletonAvatar size / lines"sm" | "md" | "lg" / 0 | 1 | 2default: "md" / 2Avatar size and text lines next to it.SkeletonCard mediabooleandefault: trueInclude the 16:9 media block.SkeletonTable rows / columnsnumberdefault: 5 / 4Grid size.labelstringdefault: "Loading"Every preset is its own status region with this label.
Accessibility
- Skeleton blocks are hidden from assistive tech; there is nothing to read in a grey bar.
- SkeletonGroup and the presets are a role="status" region with one visually hidden label, so a screen reader hears "Loading invoices" once instead of nothing or noise.
- Set aria-busy on the region that is loading and remove it when content arrives.
- The shimmer is a slow, low-contrast sweep and stops entirely under prefers-reduced-motion.
- Colors mix the foreground into transparency, so placeholders read on every surface in light and dark.