Skeleton
Overview
The skeleton component displays animated placeholder content while data is loading. It provides users with a visual indication that content is loading and gives them a sense of the structure and layout of the upcoming content. The component includes multiple variants, customizable sizing, and respects user motion preferences.
When to Use
✅ Use skeletons when:
- Loading content that takes more than a few hundred milliseconds
- Displaying placeholder content during data fetching
- Maintaining layout structure during loading states
- Creating a smooth transition between loading and loaded states
- Providing visual feedback for better perceived performance
❌ Don't use skeletons for:
- Very fast loading content (under 200ms)
- Simple operations where a spinner is more appropriate
- One-time actions like form submissions
- Loading states that need specific progress information
Examples
Variants & Types
The skeleton component supports three main variants to match different content types:
<>
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Text Skeleton</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="75%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="50%"></dap-ds-skeleton>
</dap-ds-stack>
</div>
<div>
<dap-ds-typography variant="h4">Circular Skeleton</dap-ds-typography>
<dap-ds-skeleton variant="circular"></dap-ds-skeleton>
</div>
<div>
<dap-ds-typography variant="h4">Rectangular Skeleton</dap-ds-typography>
<dap-ds-skeleton variant="rectangular"></dap-ds-skeleton>
</div>
</dap-ds-stack>
</>
Custom Sizing
Control skeleton dimensions with the width and height properties to match your content layout:
<>
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Custom Text Sizes</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text" width="100%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="85%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="65%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="40%"></dap-ds-skeleton>
</dap-ds-stack>
</div>
<div>
<dap-ds-typography variant="h4">Custom Circular Sizes</dap-ds-typography>
<dap-ds-stack direction="row" align="center">
<dap-ds-skeleton variant="circular" width="60px" height="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" width="48px" height="48px"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" width="32px" height="32px"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" width="24px" height="24px"></dap-ds-skeleton>
</dap-ds-stack>
</div>
<div>
<dap-ds-typography variant="h4">Custom Rectangular Sizes</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="rectangular" width="300px" height="200px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="250px" height="100px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="200px" height="50px"></dap-ds-skeleton>
</dap-ds-stack>
</div>
</dap-ds-stack>
</>
Real-World Patterns
User Profile Card Loading
<div style={{border: 'var(--dds-border-width-base) solid var(--dds-border-neutral-subtle)', padding: 'var(--dds-spacing-600)', borderRadius: 'var(--dds-radius-base)', maxWidth: '320px'}}>
<dap-ds-stack direction="row" align="center">
<dap-ds-skeleton variant="circular" width="48px" height="48px"></dap-ds-skeleton>
<dap-ds-stack>
<dap-ds-skeleton variant="text" width="120px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
<div style={{marginTop: 'var(--dds-spacing-400)'}}>
<dap-ds-skeleton variant="text" width="100%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="85%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60%"></dap-ds-skeleton>
</div>
<div style={{marginTop: 'var(--dds-spacing-500)', display: 'flex', gap: 'var(--dds-spacing-300)'}}>
<dap-ds-skeleton variant="rectangular" width="80px" height="32px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="100px" height="32px"></dap-ds-skeleton>
</div>
</div>
Article List Loading
<div style={{border: 'var(--dds-border-width-base) solid var(--dds-border-neutral-subtle)', padding: 'var(--dds-spacing-600)', borderRadius: 'var(--dds-radius-base)'}}>
<dap-ds-stack>
<dap-ds-typography variant="h4">Latest Articles</dap-ds-typography>
<dap-ds-stack>
<dap-ds-stack direction="row">
<dap-ds-skeleton variant="rectangular" width="120px" height="80px"></dap-ds-skeleton>
<dap-ds-stack style={{flex: 1}}>
<dap-ds-skeleton variant="text" width="100%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="85%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60%"></dap-ds-skeleton>
<dap-ds-stack direction="row" align="center" style={{marginTop: 'var(--dds-spacing-300)'}}>
<dap-ds-skeleton variant="circular" width="20px" height="20px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
<dap-ds-stack direction="row">
<dap-ds-skeleton variant="rectangular" width="120px" height="80px"></dap-ds-skeleton>
<dap-ds-stack style={{flex: 1}}>
<dap-ds-skeleton variant="text" width="95%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="65%"></dap-ds-skeleton>
<dap-ds-stack direction="row" align="center" style={{marginTop: 'var(--dds-spacing-300)'}}>
<dap-ds-skeleton variant="circular" width="20px" height="20px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
<dap-ds-stack direction="row">
<dap-ds-skeleton variant="rectangular" width="120px" height="80px"></dap-ds-skeleton>
<dap-ds-stack style={{flex: 1}}>
<dap-ds-skeleton variant="text" width="90%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="75%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="55%"></dap-ds-skeleton>
<dap-ds-stack direction="row" align="center" style={{marginTop: 'var(--dds-spacing-300)'}}>
<dap-ds-skeleton variant="circular" width="20px" height="20px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
</div>
Data Table Loading
<div style={{border: 'var(--dds-border-width-base) solid var(--dds-border-neutral-subtle)', padding: 'var(--dds-spacing-600)', borderRadius: 'var(--dds-radius-base)'}}>
<dap-ds-stack>
<dap-ds-typography variant="h4">User Management</dap-ds-typography>
<div style={{display: 'grid', gridTemplateColumns: '1fr 2fr 1fr 1fr 60px', gap: 'var(--dds-spacing-400)', alignItems: 'center'}}>
{}
<dap-ds-skeleton variant="text" width="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="70px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="40px"></dap-ds-skeleton>
{}
<dap-ds-skeleton variant="circular" width="32px" height="32px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="100%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="80%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="24px" height="24px"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" width="32px" height="32px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="85%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="90%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="24px" height="24px"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" width="32px" height="32px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="95%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="75%"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="60px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="24px" height="24px"></dap-ds-skeleton>
</div>
</dap-ds-stack>
</div>
Form Loading State
<div style={{border: 'var(--dds-border-width-base) solid var(--dds-border-neutral-subtle)', padding: 'var(--dds-spacing-600)', borderRadius: 'var(--dds-radius-base)', maxWidth: '400px'}}>
<dap-ds-stack>
<dap-ds-skeleton variant="text" width="150px"></dap-ds-skeleton>
<dap-ds-stack spacing="400">
<dap-ds-stack spacing="200">
<dap-ds-skeleton variant="text" width="100px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="100%" height="40px"></dap-ds-skeleton>
</dap-ds-stack>
<dap-ds-stack spacing="200">
<dap-ds-skeleton variant="text" width="80px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="100%" height="40px"></dap-ds-skeleton>
</dap-ds-stack>
<dap-ds-stack spacing="200">
<dap-ds-skeleton variant="text" width="120px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="100%" height="120px"></dap-ds-skeleton>
</dap-ds-stack>
<dap-ds-stack direction="row" align="center">
<dap-ds-skeleton variant="rectangular" width="20px" height="20px"></dap-ds-skeleton>
<dap-ds-skeleton variant="text" width="200px"></dap-ds-skeleton>
</dap-ds-stack>
<dap-ds-stack direction="row">
<dap-ds-skeleton variant="rectangular" width="100px" height="40px"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" width="80px" height="40px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
</dap-ds-stack>
</div>
Animation Types
The skeleton component supports different animation types to match your design needs:
Wave Animation (Default)
The wave animation creates a sliding shimmer effect across the skeleton:
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Wave Animation</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text" animation="wave"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" animation="wave"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" animation="wave" width="200px" height="100px"></dap-ds-skeleton>
</dap-ds-stack>
</div>
</dap-ds-stack>
Pulse Animation
The pulse animation creates a breathing effect with opacity changes:
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Pulse Animation</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text" animation="pulse"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" animation="pulse"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" animation="pulse" width="200px" height="100px"></dap-ds-skeleton>
</dap-ds-stack>
</div>
</dap-ds-stack>
Custom Animation
Create your own animations using the custom-keyframes attribute. This allows you to define custom CSS keyframes directly:
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Custom Animations</dap-ds-typography>
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="body">Bounce Animation</dap-ds-typography>
<dap-ds-skeleton
variant="text"
animation="custom"
custom-keyframes="0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-3px); } 60% { transform: translateY(-1px); }">
</dap-ds-skeleton>
</div>
<div>
<dap-ds-typography variant="body">Scale Animation</dap-ds-typography>
<dap-ds-skeleton
variant="circular"
animation="custom"
custom-keyframes="0% { transform: scale(0.5); } 50% { transform: scale(1.05); } 100% { transform: scale(0.5); }">
</dap-ds-skeleton>
</div>
</dap-ds-stack>
</div>
</dap-ds-stack>
Animation Comparison
Compare different animation types side by side:
<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 'var(--dds-spacing-600)', alignItems: 'start'}}>
<dap-ds-stack direction="column" align="center">
<dap-ds-typography variant="h4">Wave</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text" animation="wave"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" animation="wave"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" animation="wave" width="120px" height="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
<dap-ds-stack direction="column" align="center">
<dap-ds-typography variant="h4">Pulse</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text" animation="pulse"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular" animation="pulse"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular" animation="pulse" width="120px" height="80px"></dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
<dap-ds-stack direction="column" align="center">
<dap-ds-typography variant="h4">Custom</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton variant="text"
style={{
'--dds-skeleton-color': 'red',
}}
animation="custom" custom-keyframes="0% { transform: skew(-50deg); } 50% { transform: skew(2deg); } 100% { transform: skew(-50deg); }"></dap-ds-skeleton>
<dap-ds-skeleton variant="circular"
animation="custom" custom-keyframes="0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }"></dap-ds-skeleton>
<dap-ds-skeleton variant="rectangular"
style={{
'--dds-skeleton-color': 'red',
}}
animation="custom" custom-keyframes="0% { border-radius: 4px; } 50% { border-radius: 20px; } 100% { border-radius: 4px; }" width="120px" height="80px">
</dap-ds-skeleton>
</dap-ds-stack>
</dap-ds-stack>
</div>
Custom Styling
The skeleton component supports extensive customization through CSS custom properties and parts.
Quick Customization with CSS Custom Properties
For simple customizations, use CSS custom properties directly on the component:
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Custom Colors</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton
variant="text"
style={{
'--dds-skeleton-color': 'linear-gradient(90deg, transparent, rgb(59 130 246 / 20%), transparent)',
}}>
</dap-ds-skeleton>
<dap-ds-skeleton
variant="text"
width="75%"
style={{
'--dds-skeleton-color': 'linear-gradient(90deg, transparent, rgb(34 197 94 / 20%), transparent)',
}}>
</dap-ds-skeleton>
</dap-ds-stack>
</div>
<div>
<dap-ds-typography variant="h4">Custom Animation Speed</dap-ds-typography>
<dap-ds-stack direction="column">
<dap-ds-skeleton
variant="text"
style={{
'--dds-skeleton-animation-duration': '0.8s',
}}>
</dap-ds-skeleton>
<dap-ds-skeleton
variant="text"
width="75%"
style={{
'--dds-skeleton-animation-duration': '2.5s',
}}>
</dap-ds-skeleton>
</dap-ds-stack>
</div>
</dap-ds-stack>
Advanced Styling with CSS Parts and Custom Styles
Experiment with custom skeleton styling using CSS parts and the custom-styles attribute. The skeleton component exposes the base CSS part for advanced styling. Try the presets below or create your own styles:
Using Custom Styles Attribute
You can also use the custom-styles attribute to inject custom CSS directly into the component's shadow DOM:
<dap-ds-stack direction="column">
<div>
<dap-ds-typography variant="h4">Custom Styles with Border</dap-ds-typography>
<dap-ds-skeleton
variant="rectangular"
width="250px"
height="100px"
custom-styles="
.skeleton {
border: 2px solid var(--dds-brand-600);
border-radius: var(--dds-radius-large);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
">
</dap-ds-skeleton>
</div>
<div>
<dap-ds-typography variant="h4">Custom Styles with Gradient</dap-ds-typography>
<dap-ds-skeleton
variant="rectangular"
width="250px"
height="100px"
custom-styles="
.skeleton {
background: linear-gradient(135deg, var(--dds-brand-100), var(--dds-brand-200));
border-radius: var(--dds-radius-xlarge);
}
">
</dap-ds-skeleton>
</div>
<div>
<dap-ds-typography variant="h4">Custom Animation with Custom Styles</dap-ds-typography>
<dap-ds-skeleton
variant="circular"
width="80px"
height="80px"
animation="custom"
custom-keyframes="0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(180deg) scale(1.1); } 100% { transform: rotate(360deg) scale(1); }"
custom-styles="
.skeleton {
border: 3px solid var(--dds-brand-400);
box-shadow: 0 0 12px var(--dds-brand-300);
}
">
</dap-ds-skeleton>
</div>
</dap-ds-stack>
Best Practices
Performance Considerations
- Use skeletons for content that takes longer than 200-300ms to load
- Prefer CSS animations over JavaScript for better performance
- Avoid too many animated skeletons on a single page
Accessibility Guidelines
- Skeletons automatically include
role="status" and aria-label="Loading..."
- The component respects
prefers-reduced-motion settings
- Ensure skeletons are replaced with actual content in a timely manner
- Provide alternative loading indicators for screen readers when needed
Design Guidelines
- Match skeleton shapes and sizes to the actual content layout
- Keep animation subtle and not distracting
- Use consistent skeleton patterns across your application
- Consider the user's context when choosing between animated and static skeletons
Content Matching
- Text skeletons should approximate actual text line heights
- Circular skeletons work well for avatars and profile pictures
- Rectangular skeletons are ideal for images, cards, and buttons
- Use width variations to create realistic text patterns
Importing
Importing React
Tree-Shakeable Imports
For optimal bundle sizes, use the tree-shakeable import syntax:
Attributes
| Property | Type | Default | Description |
|---|
variant | "text", "circular" , "rectangular" | 'text' | The variant of the skeleton. |
width | string, undefined | | The width of the skeleton. Can be any valid CSS width value. |
height | string, undefined | | The height of the skeleton. Can be any valid CSS height value. |
noAnimation | boolean | false | Whether to animate the skeleton. |
animation | "wave", "pulse" , "custom" | 'wave' | The animation type for the skeleton. |
customKeyframes | string, undefined | | Custom keyframes for the animation when animation="custom". |
| Should be a valid CSS keyframes string without the | | | |
Slots
No slots available.
Events
No custom events available.
CSS Parts
| Part Name | Description |
|---|
base | The main skeleton container. |
How to Use CSS Parts
You can style CSS parts using the ::part() pseudo-element selector:
Example usage:
CSS parts allow you to style internal elements of the component while maintaining encapsulation. Learn more in our styling guide.
CSS Custom Properties
| Property Name | Description |
|---|
--dds-skeleton-color | The base color of the skeleton (default: linear-gradient(90deg, transparent, rgb(0 0 0 / 10%), transparent)) |
--dds-skeleton-animation-duration | Duration of the loading animation (default: 1.5s) |
--dds-skeleton-border-radius | Border radius for rectangular skeletons (default: var(--dds-radius-small)) |
--dds-skeleton-text-spacing | Spacing between text lines in text variant (default: var(--dds-spacing-100)) |
--dds-skeleton-animation-timing-function | Timing function for the loading animation (default: ease-in-out) |
How to Use CSS Custom Properties
CSS custom properties (CSS variables) can be set directly on the component or in your stylesheet:
Method 1: Inline styles (Quick customization)
Method 2: CSS classes (Reusable styles)
Method 3: Global theme customization
CSS custom properties inherit through the Shadow DOM, making them perfect for theming. Changes apply immediately without rebuilding.