Skeleton

@sushindustries/ui

The wait, drawn as the thing being waited for: a line, a block or a circle, shimmering unless motion is reduced.

5 min read
View as MarkdownEdit on GitHub

Examples are the tab where the component is shown doing a job, not demonstrating a prop. The API tab already lists the props.

Viewport width
Phone22remthe floor, the narrowest this promises to work at
tanstacktanstack add https://adamjurek.com/r/tanstack/skeleton.jsonshadcnpnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/skeleton.json

Press Compare. The frames are real viewports, so a layout that breaks at 320 breaks here too rather than in somebody's hands.

In a page

A card grid before its data has arrived, shaped like the cards it is about to become - same avatar circle, same two lines, same gap - so nothing jumps when the real content lands.

import { Skeleton } from "@sushindustries/ui";

export function PackageGridLoading() {
	return (
		<div className="grid gap-4" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
			{Array.from({ length: 6 }, (_, index) => (
				<div key={index} className="card p-4 flex flex-col gap-3">
					<Skeleton shape="circle" />
					<Skeleton shape="line" width="80%" />
					<Skeleton shape="line" width="50%" />
				</div>
			))}
		</div>
	);
}
tsx

What this example is not

The count and shape here match one specific grid. A real loading state should mirror whatever it is standing in for - matching card count is a guess made easier, not a rule the component enforces.