Skeleton
Material Design 3 skeleton loader component for content loading states
Skeleton
Skeletons reserve space while a loading region is being populated. Put aria-busy="true" on the loading region and hide decorative skeleton shapes with aria-hidden="true"; the CSS does not infer application loading state.
Skeleton loaders provide a visual placeholder for content that is loading. They help improve perceived performance and reduce layout shifts by showing users where content will appear.
Basic Usage
Basic Skeleton
<section aria-busy="true" aria-label="Loading profile">
<div class="skeleton" aria-hidden="true" style="width: 200px; height: 20px;"></div>
</section>Animation Types
Pulse Animation (Default)
The default pulse animation gently fades the skeleton in and out.
Pulse Animation
<div class="skeleton" style="width: 300px; height: 100px;"></div>Wave Animation
The wave animation creates a shimmer effect that moves across the skeleton.
Wave Animation
<div class="skeleton skeleton-wave" style="width: 300px; height: 100px;"></div>Static (No Animation)
Use the static variant when you need a skeleton without animation.
Static Skeleton
<div class="skeleton skeleton-static" style="width: 300px; height: 100px;"></div>Shapes
Text Skeleton
Text skeletons are optimized for loading text content with appropriate heights and spacing.
Text Skeleton Variants
<!-- Default text -->
<div class="skeleton skeleton-text"></div>
<!-- Small text -->
<div class="skeleton skeleton-text skeleton-text-sm"></div>
<!-- Large text -->
<div class="skeleton skeleton-text skeleton-text-lg"></div>Text Width Variants
Control the width of text skeletons to create realistic loading patterns:
Text Width Variants
<div class="skeleton skeleton-text skeleton-w-full"></div>
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-w-1/2"></div>
<div class="skeleton skeleton-text skeleton-w-1/4"></div>Circle Skeleton
Perfect for avatars and circular images.
Circle Skeleton Sizes
<!-- Default circle (3rem) -->
<div class="skeleton skeleton-circle"></div>
<!-- Small circle (2rem) -->
<div class="skeleton skeleton-circle skeleton-circle-sm"></div>
<!-- Large circle (4rem) -->
<div class="skeleton skeleton-circle skeleton-circle-lg"></div>
<!-- Extra large circle (6rem) -->
<div class="skeleton skeleton-circle skeleton-circle-xl"></div>Rectangle Skeleton
Use for images, cards, or large content blocks.
Rectangle Skeleton Sizes
<!-- Default rectangle (12rem height) -->
<div class="skeleton skeleton-rect"></div>
<!-- Small rectangle (8rem height) -->
<div class="skeleton skeleton-rect skeleton-rect-sm"></div>
<!-- Large rectangle (16rem height) -->
<div class="skeleton skeleton-rect skeleton-rect-lg"></div>Button Skeleton
Optimized for button-shaped placeholders.
Button Skeleton Sizes
<!-- Default button -->
<div class="skeleton skeleton-button"></div>
<!-- Small button -->
<div class="skeleton skeleton-button skeleton-button-sm"></div>
<!-- Large button -->
<div class="skeleton skeleton-button skeleton-button-lg"></div>Input Skeleton
For form input placeholders.
Input Skeleton
<div class="skeleton skeleton-input"></div>Border Radius Variants
Rounded
Rounded Skeleton
<div class="skeleton skeleton-rounded" style="width: 200px; height: 100px;"></div>Rounded Full
Rounded Full Skeleton
<div class="skeleton skeleton-rounded-full" style="width: 200px; height: 50px;"></div>Common Patterns
Avatar with Text
Create a skeleton for user profiles or list items.
Avatar with Text Pattern
<div class="skeleton-avatar-text">
<div class="skeleton skeleton-circle"></div>
<div class="skeleton-group" style="flex: 1;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
</div>
</div>List Item
List Item Pattern
<div class="skeleton-list-item">
<div class="skeleton skeleton-circle skeleton-circle-sm"></div>
<div class="skeleton-group" style="flex: 1;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
</div>
</div>Card Skeleton
Card Skeleton Pattern
<div class="skeleton-card">
<div class="skeleton skeleton-rect skeleton-rect-sm"></div>
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-w-full"></div>
<div class="skeleton skeleton-text skeleton-w-1/2"></div>
</div>
</div>Skeleton Group
Group multiple skeletons with consistent spacing.
Skeleton Group
<div class="skeleton-group">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-w-1/2"></div>
</div>Complex Examples
Article Loading
Article Loading Pattern
<article>
<!-- Header -->
<div class="skeleton-group">
<div class="skeleton skeleton-text skeleton-text-lg skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
</div>
<!-- Featured image -->
<div class="skeleton skeleton-rect" style="margin: 1.5rem 0;"></div>
<!-- Content -->
<div class="skeleton-group">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
</div>
</article>User Profile Card
User Profile Card Pattern
<div class="skeleton-card">
<!-- Cover image -->
<div class="skeleton skeleton-rect skeleton-rect-sm"></div>
<!-- Avatar -->
<div style="margin-top: -2rem; padding: 0 1rem;">
<div class="skeleton skeleton-circle skeleton-circle-xl"></div>
</div>
<!-- Profile info -->
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text skeleton-text-lg skeleton-w-1/2"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-3/4"></div>
<div style="display: flex; gap: 0.5rem; margin-top: 1rem;">
<div class="skeleton skeleton-button skeleton-button-sm"></div>
<div class="skeleton skeleton-button skeleton-button-sm"></div>
</div>
</div>
</div>Product Grid
Product Grid Pattern
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem;">
<!-- Product card 1 -->
<div class="skeleton-card">
<div class="skeleton skeleton-rect skeleton-rect-sm skeleton-wave"></div>
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
<div class="skeleton skeleton-button skeleton-button-sm"></div>
</div>
</div>
<!-- Product card 2 -->
<div class="skeleton-card">
<div class="skeleton skeleton-rect skeleton-rect-sm skeleton-wave"></div>
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
<div class="skeleton skeleton-button skeleton-button-sm"></div>
</div>
</div>
<!-- Product card 3 -->
<div class="skeleton-card">
<div class="skeleton skeleton-rect skeleton-rect-sm skeleton-wave"></div>
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
<div class="skeleton skeleton-button skeleton-button-sm"></div>
</div>
</div>
</div>Best Practices
Match Content Structure
Create skeleton layouts that closely match the actual content structure to minimize layout shifts.
Matching Content Structure
<!-- Good: Matches the actual content structure -->
<div class="skeleton-avatar-text">
<div class="skeleton skeleton-circle"></div>
<div class="skeleton-group" style="flex: 1;">
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-1/2"></div>
</div>
</div>Use Appropriate Animation
- Pulse: Best for small, quick-loading content
- Wave: Best for larger content blocks and images
- Static: Use when animation might be distracting or for accessibility
Animation Types
<!-- Quick loading items: pulse (default) -->
<div class="skeleton skeleton-text"></div>
<!-- Large images: wave -->
<div class="skeleton skeleton-rect skeleton-wave"></div>
<!-- Reduced motion preference: static -->
<div class="skeleton skeleton-static skeleton-text"></div>Progressive Loading
Load content progressively to improve perceived performance.
Progressive Loading
<div class="skeleton-card">
<!-- Load header first -->
<div class="skeleton-group">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-text-sm skeleton-w-3/4"></div>
</div>
<!-- Then load image -->
<div class="skeleton skeleton-rect skeleton-wave" style="margin-top: 1rem;"></div>
<!-- Finally load additional content -->
<div class="skeleton-group" style="margin-top: 1rem;">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text skeleton-w-3/4"></div>
</div>
</div>Accessibility
Consider users with motion sensitivity by providing a static option:
<!-- Respecting prefers-reduced-motion -->
<style>
@media (prefers-reduced-motion: reduce) {
.skeleton {
animation: none;
}
.skeleton-wave::after {
animation: none;
}
}
</style>
Framework Examples
React
interface SkeletonProps {
variant?: 'text' | 'circle' | 'rect' | 'button' | 'input';
animation?: 'pulse' | 'wave' | 'static';
width?: string;
height?: string;
className?: string;
}
export function Skeleton({
variant = 'text',
animation = 'pulse',
width,
height,
className = ''
}: SkeletonProps) {
const classes = [
'skeleton',
variant !== 'text' ? `skeleton-${variant}` : 'skeleton-text',
animation !== 'pulse' ? `skeleton-${animation}` : '',
className
].filter(Boolean).join(' ');
const style = {
...(width && { width }),
...(height && { height })
};
return <div className={classes} style={style} />;
}
// Usage
<div>
<Skeleton variant="circle" width="48px" height="48px" />
<Skeleton variant="text" width="200px" />
<Skeleton variant="rect" animation="wave" height="200px" />
</div>
Vue
<template>
<div :class="classes" :style="style" />
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
variant: {
type: String,
default: 'text',
validator: (value) => ['text', 'circle', 'rect', 'button', 'input'].includes(value)
},
animation: {
type: String,
default: 'pulse',
validator: (value) => ['pulse', 'wave', 'static'].includes(value)
},
width: String,
height: String
})
const classes = computed(() => {
return [
'skeleton',
props.variant !== 'text' ? `skeleton-${props.variant}` : 'skeleton-text',
props.animation !== 'pulse' ? `skeleton-${props.animation}` : ''
].filter(Boolean).join(' ')
})
const style = computed(() => ({
...(props.width && { width: props.width }),
...(props.height && { height: props.height })
}))
</script>
<!-- Usage -->
<Skeleton variant="circle" width="48px" height="48px" />
<Skeleton variant="text" width="200px" />
<Skeleton variant="rect" animation="wave" height="200px" />
Svelte
<script>
export let variant = 'text';
export let animation = 'pulse';
export let width = undefined;
export let height = undefined;
$: classes = [
'skeleton',
variant !== 'text' ? `skeleton-${variant}` : 'skeleton-text',
animation !== 'pulse' ? `skeleton-${animation}` : ''
].filter(Boolean).join(' ');
$: style = [
width && `width: ${width}`,
height && `height: ${height}`
].filter(Boolean).join('; ');
</script>
<div class={classes} {style} />
<!-- Usage -->
<Skeleton variant="circle" width="48px" height="48px" />
<Skeleton variant="text" width="200px" />
<Skeleton variant="rect" animation="wave" height="200px" />
API Reference
Base Classes
| Class | Description |
|---|---|
.skeleton |
Base skeleton styles (required) |
.skeleton-wave |
Wave animation variant |
.skeleton-static |
No animation variant |
Shape Variants
| Class | Description |
|---|---|
.skeleton-text |
Text line skeleton (1rem height) |
.skeleton-text-sm |
Small text skeleton (0.875rem height) |
.skeleton-text-lg |
Large text skeleton (1.25rem height) |
.skeleton-circle |
Circle skeleton (3rem diameter) |
.skeleton-circle-sm |
Small circle (2rem diameter) |
.skeleton-circle-lg |
Large circle (4rem diameter) |
.skeleton-circle-xl |
Extra large circle (6rem diameter) |
.skeleton-rect |
Rectangle skeleton (12rem height) |
.skeleton-rect-sm |
Small rectangle (8rem height) |
.skeleton-rect-lg |
Large rectangle (16rem height) |
.skeleton-button |
Button skeleton (2.5rem height, 6rem width) |
.skeleton-button-sm |
Small button (2rem height, 5rem width) |
.skeleton-button-lg |
Large button (3rem height, 8rem width) |
.skeleton-input |
Input skeleton (2.75rem height) |
Width Variants
| Class | Description |
|---|---|
.skeleton-w-full |
Full width (100%) |
.skeleton-w-3/4 |
75% width |
.skeleton-w-1/2 |
50% width |
.skeleton-w-1/4 |
25% width |
Border Radius
| Class | Description |
|---|---|
.skeleton-rounded |
Medium border radius (0.75rem) |
.skeleton-rounded-full |
Fully rounded (9999px) |
Layout Helpers
| Class | Description |
|---|---|
.skeleton-group |
Container with vertical spacing (1rem gap) |
.skeleton-avatar-text |
Horizontal layout for avatar + text |
.skeleton-card |
Card container with padding and background |
.skeleton-list-item |
List item layout with padding |
Combinations
You can combine classes for different effects:
Class Combinations
<!-- Wave animation with custom size -->
<div class="skeleton skeleton-wave" style="width: 300px; height: 150px;"></div>
<!-- Static text skeleton -->
<div class="skeleton skeleton-text skeleton-static skeleton-w-3/4"></div>
<!-- Large circle with wave animation -->
<div class="skeleton skeleton-circle skeleton-circle-lg skeleton-wave"></div>