Skeleton
Placeholder shapes for content that's loading, sized with classes and pulsing only when motion is allowed.
stableSkeleton.tsx
Examples
Profile row
skeleton/basic.tsx
import { Skeleton } from "@streamuilib/react";
export default function SkeletonBasic() {
return (
<div role="status" aria-busy="true" aria-label="Loading profile" className="flex w-full max-w-xs items-center gap-4">
<Skeleton className="size-12 rounded-full" />
<div className="flex flex-1 flex-col gap-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
</div>
</div>
);
}Loading card
skeleton/card.tsx
import { Card, CardContent, CardFooter, CardHeader, Skeleton } from "@streamuilib/react";
export default function SkeletonCard() {
return (
<Card role="status" aria-busy="true" aria-label="Loading plan" className="w-full max-w-sm">
<CardHeader>
<Skeleton className="h-5 w-1/3" />
<Skeleton className="h-3 w-2/3" />
</CardHeader>
<CardContent className="flex flex-col gap-2">
<Skeleton className="h-8 w-1/2" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-5/6" />
</CardContent>
<CardFooter>
<Skeleton className="h-11 w-full" />
</CardFooter>
</Card>
);
}Installation
bun add @streamuilib/reactusage.tsx
import { Skeleton } from "@streamuilib/react";
<div role="status" aria-busy="true" aria-label="Loading">
<Skeleton className="h-4 w-40" />
</div>API
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Size and shape, e.g. h-4 w-40 or size-12 rounded-full. |
| ...props | ComponentProps<"div"> | — | Native div attributes. |
Accessibility
- Each skeleton is aria-hidden; label the loading region instead (role="status", aria-busy, aria-label) so screen readers hear one message.
- The pulse only runs when the user allows motion.
Source
packages/react/src/Skeleton.tsx
import type { ComponentProps } from "react";
import { cn } from "./cn";
/**
* A placeholder block for content that's loading. Size it with classes
* (`h-4 w-40`, `size-12 rounded-full`). It's hidden from screen readers;
* mark the loading region with `aria-busy` and give it a label instead.
*/
export function Skeleton({ className, ...props }: ComponentProps<"div">) {
return (
<div
aria-hidden
className={cn("border-2 border-ink-faint bg-paper-sunken motion-safe:animate-pulse", className)}
{...props}
/>
);
}