Build Assets
UI Component
Skeleton Loader
Animated placeholder that mirrors the shape of loading content.
Platform: bothTags: skeleton, loading, placeholder, shimmer
Purpose
Represents content that is loading by showing a rough structural approximation. Reduces perceived wait time compared to a spinner by giving users a preview of the layout. Use when loading time is between 300ms and 3 seconds.
Anatomy
- Shape blocks: rectangles and circles matching the content they replace
- Shimmer animation: left-to-right gradient sweep, 1.5s loop
- Color: var(--color-bg-secondary) base with slightly lighter shimmer highlight
States
- Loading: shimmer animation active
- Loaded: replaced by real content (no transition needed)
- Error: replaced by error state
Tokens
spacing
borderRadius: 4px (text), 8px (cards), 999px (avatars/badges)
sizing
shimmerDuration: 1.5sshimmerEasing: ease-in-out
colors
base: var(--color-bg-secondary)highlight: var(--color-bg-hover)
Do
- Mirror the real layout as closely as possible — same proportions, same structure
- Use for content that loads in 300ms–3s — under 300ms show nothing, over 3s show a spinner with progress
- Keep the number of skeleton shapes close to the number of real content items
- Animate with a horizontal shimmer sweep — it signals active loading
Don't
- Use skeletons for instant operations (< 300ms) — the flash is jarring
- Show skeleton shapes that don't resemble the actual content
- Use spinner AND skeleton simultaneously
- Show skeleton indefinitely — switch to error state after timeout
Accessibility
- aria-busy="true" on the container while loading
- aria-label="Loading..." on the skeleton container
- Remove animation for users with prefers-reduced-motion
Code Example
/* Shimmer animation */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg,
var(--color-bg-secondary) 25%,
var(--color-bg-hover) 50%,
var(--color-bg-secondary) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
}