Skeleton
Loading placeholder components for indicating content is being loaded.
Import
import { Skeleton, ChartSkeleton, TableSkeleton, FormSkeleton } from '@/components/ui';
Basic Usage
<Skeleton className="h-4 w-32" />
Components
| Component |
Description |
Skeleton |
Base skeleton element |
ChartSkeleton |
Pre-built chart placeholder |
TableSkeleton |
Pre-built table placeholder |
FormSkeleton |
Pre-built form placeholder |
Skeleton Props
| Prop |
Type |
Default |
Description |
className |
string |
- |
Size and shape classes |
Basic Shapes
{/* Text line */}
<Skeleton className="h-4 w-full" />
{/* Short line */}
<Skeleton className="h-4 w-32" />
{/* Avatar circle */}
<Skeleton className="h-10 w-10 rounded-full" />
{/* Image placeholder */}
<Skeleton className="h-48 w-full" />
{/* Button placeholder */}
<Skeleton className="h-10 w-24 rounded-lg" />
Card Skeleton
export function CardSkeleton() {
return (
<Card className="p-6 space-y-4">
<div className="flex items-center gap-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-24" />
</div>
</div>
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
</Card>
);
}
Pre-built Skeletons
ChartSkeleton
<ChartSkeleton />
<ChartSkeleton height={400} />
TableSkeleton
<TableSkeleton />
<TableSkeleton rows={10} />
FormSkeleton
<FormSkeleton />
Loading State Pattern
'use client';
import { useQuery } from '@tanstack/react-query';
import { Skeleton, Card } from '@/components/ui';
export function UserCard({ userId }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
if (isLoading) {
return (
<Card className="p-6">
<div className="flex items-center gap-4">
<Skeleton className="h-12 w-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-24" />
</div>
</div>
</Card>
);
}
return (
<Card className="p-6">
<div className="flex items-center gap-4">
<Avatar src={user.avatar} name={user.name} size="lg" />
<div>
<h3 className="font-semibold">{user.name}</h3>
<p className="text-sm text-zinc-500">{user.email}</p>
</div>
</div>
</Card>
);
}
List Skeleton
export function ListSkeleton({ count = 5 }) {
return (
<div className="space-y-4">
{Array.from({ length: count }).map((_, i) => (
<div key={i} className="flex items-center gap-4">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="flex-1 space-y-2">
<Skeleton className="h-4 w-1/2" />
<Skeleton className="h-3 w-1/3" />
</div>
</div>
))}
</div>
);
}
Dashboard Skeleton
export function DashboardSkeleton() {
return (
<div className="space-y-6">
{/* Stats */}
<div className="grid grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<Card key={i} className="p-6">
<Skeleton className="h-4 w-24 mb-2" />
<Skeleton className="h-8 w-32" />
</Card>
))}
</div>
{/* Chart */}
<ChartSkeleton />
{/* Table */}
<TableSkeleton rows={5} />
</div>
);
}
Styling
- Background: Zinc-200 (light) / Zinc-700 (dark)
- Animation: Pulse animation
- Border radius: Configurable via className