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

Animation Types

Pulse Animation (Default)

The default pulse animation gently fades the skeleton in and out.

Pulse Animation

Wave Animation

The wave animation creates a shimmer effect that moves across the skeleton.

Wave Animation

Static (No Animation)

Use the static variant when you need a skeleton without animation.

Static Skeleton

Shapes

Text Skeleton

Text skeletons are optimized for loading text content with appropriate heights and spacing.

Text Skeleton Variants

Text Width Variants

Control the width of text skeletons to create realistic loading patterns:

Text Width Variants

Circle Skeleton

Perfect for avatars and circular images.

Circle Skeleton Sizes

Rectangle Skeleton

Use for images, cards, or large content blocks.

Rectangle Skeleton Sizes

Button Skeleton

Optimized for button-shaped placeholders.

Button Skeleton Sizes

Input Skeleton

For form input placeholders.

Input Skeleton

Border Radius Variants

Rounded

Rounded Skeleton

Rounded Full

Rounded Full Skeleton

Common Patterns

Avatar with Text

Create a skeleton for user profiles or list items.

Avatar with Text Pattern

List Item

List Item Pattern

Card Skeleton

Card Skeleton Pattern

Skeleton Group

Group multiple skeletons with consistent spacing.

Skeleton Group

Complex Examples

Article Loading

Article Loading Pattern

User Profile Card

User Profile Card Pattern

Product Grid

Product Grid Pattern

Best Practices

Match Content Structure

Create skeleton layouts that closely match the actual content structure to minimize layout shifts.

Matching Content Structure

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

Progressive Loading

Load content progressively to improve perceived performance.

Progressive Loading

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

  • Card - Container component
  • Avatar - User avatar component
  • Progress - Progress indicators

See Also