ComponentsCSS
Skeleton
Placeholder shapes with a band of light sweeping across while content loads.
Installation
npx shadcn@latest add https://ui-collections.vercel.app/r/glass/skeleton.jsonAlso installs the theme, cn(). Packages: clsx, tailwind-merge.
Usage
skeleton-demo.tsx
import { Skeleton, SkeletonText } from "@/registry/kit/ui/skeleton";
export default function SkeletonDemo() {
return (
<div className="surface grid w-full max-w-sm gap-5 rounded-card p-6" aria-busy="true" aria-label="Loading project">
<div className="flex items-center gap-4">
<Skeleton shape="circle" className="size-12" />
<div className="grid flex-1 gap-2">
<Skeleton shape="text" className="w-2/3" />
<Skeleton shape="text" className="h-3 w-1/3" />
</div>
</div>
<Skeleton className="h-28" />
<SkeletonText lines={3} />
<div className="flex gap-3">
<Skeleton className="h-9 flex-1 rounded-control" />
<Skeleton className="h-9 w-20 rounded-control" />
</div>
</div>
);
}API
shape= "block"
"block" | "text" | "circle"Corner style: field, a text line, or round for avatars.
classNamestringSize, e.g. h-28 or size-12.
<SkeletonText>{ lines?: number }A paragraph of lines; the last one is shorter.
Source
components/ui/skeleton.tsx
import type { CSSProperties } from "react";
import { cn } from "@/registry/kit/lib/utils";
const shapes = {
block: "rounded-field",
text: "h-3.5 rounded-control",
circle: "rounded-full",
};
export interface SkeletonProps {
/** `text` is one line of copy; `circle` is for avatars (size it with `size-*`). */
shape?: keyof typeof shapes;
className?: string;
style?: CSSProperties;
}
/**
* Placeholder for content that is loading: a faint shape with a band of light
* sweeping across it. Size it with classes; the sweep stops under reduced motion.
*/
export function Skeleton({ shape = "block", className, style }: SkeletonProps) {
return (
<span
aria-hidden
style={style}
className={cn(
"block bg-size-[200%_100%] animate-shimmer",
"bg-[linear-gradient(90deg,color-mix(in_oklab,var(--c-fg)_6%,transparent)_25%,color-mix(in_oklab,var(--c-fg)_12%,transparent)_50%,color-mix(in_oklab,var(--c-fg)_6%,transparent)_75%)]",
shapes[shape],
className,
)}
/>
);
}
/** A paragraph of placeholder lines; the last one is shorter, like real text. */
export function SkeletonText({ lines = 3, className }: { lines?: number; className?: string }) {
return (
<span className={cn("grid gap-2.5", className)} aria-hidden>
{Array.from({ length: lines }, (_, i) => (
<Skeleton key={i} shape="text" className={i === lines - 1 && lines > 1 ? "w-3/5" : "w-full"} />
))}
</span>
);
}