patterns
Skeletons and loading
Skeletons shape the content. Spinners belong only on button actions, infinite-scroll footers, and the first-ever boot.
Read the full specCard skeleton
Heading, meta line, media block, button row — matches a typical reports-feed card.
tsx
<div className="rounded-lg border p-6">
<Skeleton className="h-6 w-48" />
<Skeleton className="mt-2 h-4 w-32" />
<Skeleton className="mt-6 h-32 w-full rounded-md" />
<div className="mt-4 flex gap-2">
<Skeleton className="h-9 w-24" />
<Skeleton className="h-9 w-24" />
</div>
</div>Table skeleton
5–10 rows is enough. Match the column widths to the real header — don't render 100.
| Report | Area | Status | Reported |
|---|---|---|---|
tsx
<tr>
<td><Skeleton className="h-4 w-24" /></td>
<td><Skeleton className="h-4 w-32" /></td>
<td><Skeleton className="h-4 w-16" /></td>
<td><Skeleton className="h-4 w-20" /></td>
</tr>Form skeleton
Label + control stubs. Same rhythm as the real form so layout doesn't jump on arrival.
Stat-card skeleton
Label, value, sparkline — use above-the-fold on dashboards.
When spinners are fine
The exception list — everywhere else, shape the content.
- Button actionLoader2 icon inside the button while saving / sending / signing in.
- Infinite-scroll footerSmall spinner below the list as the next page fetches.
- Sub-150ms loadsDelay skeleton render by 150ms so fast loads don't flicker.
- First-ever bootRare. Under 2 seconds. Longer than that becomes a structural skeleton.