Skeleton

Material Design 3 skeleton loader component for content loading states

Skeleton

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

ClassDescription
.skeletonBase skeleton styles (required)
.skeleton-waveWave animation variant
.skeleton-staticNo animation variant

Shape Variants

ClassDescription
.skeleton-textText line skeleton (1rem height)
.skeleton-text-smSmall text skeleton (0.875rem height)
.skeleton-text-lgLarge text skeleton (1.25rem height)
.skeleton-circleCircle skeleton (3rem diameter)
.skeleton-circle-smSmall circle (2rem diameter)
.skeleton-circle-lgLarge circle (4rem diameter)
.skeleton-circle-xlExtra large circle (6rem diameter)
.skeleton-rectRectangle skeleton (12rem height)
.skeleton-rect-smSmall rectangle (8rem height)
.skeleton-rect-lgLarge rectangle (16rem height)
.skeleton-buttonButton skeleton (2.5rem height, 6rem width)
.skeleton-button-smSmall button (2rem height, 5rem width)
.skeleton-button-lgLarge button (3rem height, 8rem width)
.skeleton-inputInput skeleton (2.75rem height)

Width Variants

ClassDescription
.skeleton-w-fullFull width (100%)
.skeleton-w-3/475% width
.skeleton-w-1/250% width
.skeleton-w-1/425% width

Border Radius

ClassDescription
.skeleton-roundedMedium border radius (0.75rem)
.skeleton-rounded-fullFully rounded (9999px)

Layout Helpers

ClassDescription
.skeleton-groupContainer with vertical spacing (1rem gap)
.skeleton-avatar-textHorizontal layout for avatar + text
.skeleton-cardCard container with padding and background
.skeleton-list-itemList 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