Skeleton
What is a Skeleton?
Skeletons are loading placeholders that mimic the structure of upcoming content—gray shapes showing where text, images, and UI elements will appear. They reduce perceived wait time and prevent layout shift.
Key Principles
Match Final Layout: Skeleton shapes must mirror the actual content structure. If a card has title + 3 lines of text + image, the skeleton shows those exact placeholder shapes.
Progressive Disclosure: Replace skeleton with real content as data loads—don't wait for everything. Partial content is better than full skeleton.
Reduce Perceived Latency: Skeletons make waiting feel 30% shorter than spinners. Users process "content is coming" faster than "loading..."
Best Practices
✅ DO:
- Match skeleton dimensions to actual content
- Use subtle pulse/shimmer animation
- Create component-specific skeletons (CardSkeleton, ListSkeleton)
- Replace progressively as data arrives
- Keep skeleton code lightweight
❌ DON'T:
- Use generic spinners where skeletons fit
- Create skeletons that don't match final layout
- Animate aggressively (distracting)
- Show skeleton for fast-loading content (<200ms)
- Cause layout shift when content replaces skeleton
Code Patterns
Recommended
// CardSkeleton.jsx
const CardSkeleton = () => (
<div className="card-skeleton">
<div className="skeleton-image skeleton-pulse" />
<div className="skeleton-content">
<div className="skeleton-title skeleton-pulse" />
<div className="skeleton-text skeleton-pulse" />
<div className="skeleton-text skeleton-text--short skeleton-pulse" />
</div>
</div>
);
// Usage with loading state
const ProductCard = ({ product, loading }) => {
if (loading) return <CardSkeleton />;
return <Card product={product} />;
};
/* Skeleton animation */
.skeleton-pulse {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Avoid
// ❌ WRONG - Generic skeleton doesn't match content
const Loading = () => <div className="spinner" />;
// ✅ CORRECT - Skeleton matches actual card structure
const CardSkeleton = () => (
<div className="card">
<div className="skeleton skeleton--image" />
<div className="skeleton skeleton--title" />
<div className="skeleton skeleton--text" />
</div>
);
Related Terminologies
- Component (UI) - Skeletons are loading variants of components
- Props (UI) - Loading state often passed as prop
- SSR (Server) - Can prevent need for skeletons
- Ajax (State) - Data fetching triggers skeleton display
Quality Gates
- Skeleton matches final content structure
- Subtle animation (not distracting)
- No layout shift on content load
- Used for content taking >200ms
- Component-specific (not generic)
Source: /docs/ui/skeleton.md