Use Skeleton while content is pending, not when a request failed or returned no results. Match final-content dimensions to reduce layout movement. Skeleton does not fetch data or decide when to disappear.
Live preview
import { Skeleton } from "@/components/ui/skeleton";
export default function SkeletonDemo() {
return (
<div className="flex items-center space-x-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 sm:w-[250px] w-[100px]" />
<Skeleton className="h-4 sm:w-[200px] w-[100px]" />
</div>
</div>
);
}
#Installation
Complete the base setup. For manual installation:
First time? Install the shared base once.
npx shadcn@latest add https://neobrutal-ui.andongmin.com/r/skeleton.jsonCreate components/ui/skeleton.tsx:
import { cn } from "@/lib/utils";
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn(
"animate-pulse rounded-base border-2 border-border bg-secondary-background",
className,
)}
{...props}
/>
);
}
export { Skeleton };
#Usage
import { Skeleton } from "@/components/ui/skeleton";
export default function LoadingProfile() {
return (
<section aria-busy="true" aria-label="Profile">
<p role="status" className="sr-only">
Loading profile.
</p>
<div aria-hidden="true" className="flex items-center gap-4">
<Skeleton className="size-12 rounded-full" />
<div className="grid gap-2">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-4 w-28" />
</div>
</div>
</section>
);
}#API reference
| Prop | Purpose | Default |
|---|---|---|
className | Width, height, and shape | Caller-defined |
| Native div props | HTML attributes on placeholder | Forwarded |
aria-hidden | Hide decorative shapes | Set explicitly in example |
#Accessibility
Hide repeated shapes and expose one concise loading status for the section. Keep real context visible. Replace loading with content, an empty state, or an error message when the request finishes; an infinite placeholder is not error handling.