Skip to main content
Design system

Coming Soon

This component status is coming soon and indicates a component is in a queue for future work.

Skeleton

A skeleton state is a simplified, placeholder version of UI content that appears while the actual content is loading. It typically shows gray boxes or lines in the shape and layout of the expected content, giving users a preview of how the page will look.

Bundle size: 0
Install:
npm install @washingtonpost/wpds-ui-kit
|Copy
Usage:
import { Skeleton } from "@washingtonpost/wpds-ui-kit"
|Copy
Storybook:  View on Storybook
Source:  View on Github

Anatomy

Note: Image not to scale

  1. Shimmer: An optional visual animation effect that moves across skeleton elements to indicate a loading state.
  2. Bone: The fundamental structural element that visually represents UI components in a loading state.

Options

Bone Elements

Bone elements are customizable shapes that can be configured with different widths, heights, and border-radius properties to accurately represent various UI components. They utilize alpha tokens for color values, ensuring proper rendering in both dark mode and on different color backgrounds.

  • Blocks should be used for images, videos and cards.
  • Lines should be used for lines of text, and can be combined to create paragraphs.
  • Circles and pills should be used for buttons, avatars and icons.

Block

This variant include predefined aspect ratios in landscape or portrait orientations for consistent UI proportions. Designers and developers may also set custom dimensions for precise skeleton control.

Line

This variant includes several heights that mirror many of our font-size tokens.

Additionally, the line variant includes text-alignment options, which can be useful for building paragraph blocks.

Circle

This element offers both default and compact height options, mirroring our button component, and can have custom widths to accurately represent a button.

Shimmer Animation

Our shimmer animation uses optimized timing parameters for better user perception and system performance. It features a left-to-right gradient with a 300ms initial delay, 700ms easing duration, and 700ms delay before repeating (1700ms total cycle). These parameters were carefully chosen because:

  • 300ms Initial Delay: Balances immediate feedback with structural perception, allowing users to quickly recognize the loading pattern
  • 700ms Easing Duration: Creates smooth, noticeable motion that signals activity without rushing, aligning with human visual processing speeds.
  • 700ms Delay Before Repeating: Prevents visual fatigue and reduces perceived loading time, as periodic animation with pauses decreases user frustration.

These parameters create a balanced loading state that communicates activity without overwhelming the interface. The 1700ms cycle length produces a natural visual rhythm optimized for human perception, while the left-to-right movement intuitively indicates an active loading state.


Accessibility

Animation

The animation should be gentle and unobtrusive, providing visual feedback to users while the actual content loads and enhancing the perceived performance of the interface. For browsers or devices that don't support animations, the component should revert to a static skeleton state.


Guidance

Animated vs. Static Skeleton States

When to Use Animated Skeleton States with Shimmer:

  • Loading times > 300ms: Use shimmer animations when content typically takes longer than 300ms to load, as this is when users start noticing delay
  • Complex data fetching: Ideal for pages that require multiple API calls or heavy data processing before display
  • Progressive loading: When content loads in stages or streams, shimmer provides continuous feedback

When to Use Static Skeleton States:

  • Very quick loads: For content that loads under 300ms, static states avoid unnecessary visual noise
  • Performance constraints: On low-end devices or when CPU usage is a concern
  • Reduced motion settings: When users have indicated a preference for minimal animations
  • Simple placeholder needs: For basic content blocks where loading state feedback is less critical

Best practice is to implement shimmer animations with a fallback to static skeleton states when necessary, ensuring a robust and accessible loading experience across all scenarios.

Modular building

Our Skeleton State Component System provides a modular framework for intuitive loading states. Core elements include primitive shapes mirroring UI structures: circular/pill shapes for buttons, avatars and icons, rectangles for images, videos and cards, and lines for text. This atomic structure enables team members to build consistent loading states across our products.

The system's modularity scales from simple elements to complex structures. You can combine basic shapes into compositions that represent layout hierarchies during loading. For example, a skeleton card might use a rectangular image placeholder, multiple text lines, and a circular avatar—all from the same building blocks. This approach ensures loading states accurately reflect the final UI while adapting to design changes across different contexts and screen sizes.

Avoid using skeleton states for three situations: content that updates frequently (like notifications and real-time data), tiny UI elements, or pages that load in less than 300ms where showing content directly works better.


API Reference

SkeletonRoot

PropDescriptionTypeDefaultRequired
animation
"none" | "shimmer" | ({ "@reducedMotion"?: "none" | "shimmer"; "@sm"?: "none" | "shimmer"; "@md"?: "none" | "shimmer"; "@lg"?: "none" | "shimmer"; "@xl"?: "none" | "shimmer"; "@xxl"?: "none" | "shimmer"; ... 18 more ...; "@initial"?: "none" | "shimmer"; } & { ...; })
shimmer False
cssWPDS provides a css prop for overriding styles easily. It’s like the style attribute, but it supports tokens, media queries, nesting and token-aware values. All WPDS Components include a css prop. Use it to pass in overrides.
CSS<{ sm: `(max-width: ${string})`; md: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; lg: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xxl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; notS...
----False

SkeletonBlock

PropDescriptionTypeDefaultRequired
aspectRatio
"1:1" | "2:1" | "3:2" | "4:3" | "5:4" | "16:9" | "16:10" | "21:9"
11 False
cssWPDS provides a css prop for overriding styles easily. It’s like the style attribute, but it supports tokens, media queries, nesting and token-aware values. All WPDS Components include a css prop. Use it to pass in overrides.
CSS<{ sm: `(max-width: ${string})`; md: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; lg: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xxl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; notS...
----False
portrait
boolean
----False

SkeletonCircle

PropDescriptionTypeDefaultRequired
cssWPDS provides a css prop for overriding styles easily. It’s like the style attribute, but it supports tokens, media queries, nesting and token-aware values. All WPDS Components include a css prop. Use it to pass in overrides.
CSS<{ sm: `(max-width: ${string})`; md: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; lg: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xxl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; notS...
----False
density
"default" | "compact"
default False

SkeletonLine

PropDescriptionTypeDefaultRequired
fontSize
"100" | "112" | "125" | "150" | "175" | "200" | "250" | "075" | "087"
----False
textAlign
"left" | "right" | "center" | "justified"
justified False
cssWPDS provides a css prop for overriding styles easily. It’s like the style attribute, but it supports tokens, media queries, nesting and token-aware values. All WPDS Components include a css prop. Use it to pass in overrides.
CSS<{ sm: `(max-width: ${string})`; md: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; lg: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; xxl: `(min-width: calc(${string} + 1px)) and (max-width: ${string})`; notS...
----False
Edit this page on GitHub.