Skip to content

Skeleton

A skeleton draws the layout of content that is still on its way, such as the rows of a list or the lines of a message. The page appears at once, and the content takes each shape's place as it arrives.

Example

Loading candidates

When to use it

Use a skeleton when you know the shape of what is coming, such as a list of candidates or the notes of an interview. Draw only what changes with the data: the toolbar and the column headers show from the start.

Work of a known size takes a progress bar, and a wait inside a button takes a spinner. A page that shows a skeleton shows no spinner as well.

Anatomy

Each shape stands for one element at its real size, such as a line of text or an avatar. Rows keep the height of the real rows, so nothing moves when the content lands.

Loading message
Fill, light theme
Ink mixed 22 percent into the card
--skeleton-fill
Fill, dark theme
Ink mixed 16 percent into the card
--skeleton-fill
Line of text
12 px high
Line corner radius
4 px
--radius-sm
Paragraph
Lines at 80, 67 and 60 percent of the width
Space between lines
8 px
--space-2
Avatar
A circle at the avatar's size
Row
The height of the real row

Motion

The whole skeleton pulses together, fading to half strength and back, so no shape runs ahead of another. With reduced motion, the shapes stay still.

Pulse
Opacity 100 to 50 percent and back
Cycle
2,000 ms
Easing
cubic-bezier(0.4, 0, 0.6, 1)

Behavior

Content replaces each shape where it stands. When one part arrives before the rest, it takes its own place and the other shapes keep pulsing.

When the content turns out to be empty, the skeleton gives way to the empty state. When the load fails, it gives way to the error and Try again. After 10 seconds the skeleton stays, and a message above it says what is still loading.

Accessibility

The shapes are hidden from screen readers. The loading region is marked busy and holds one visually hidden sentence, such as “Loading candidates”, which a screen reader hears once.

When the content arrives, focus stays where it was.

Permitted and not permitted

Permitted

  • Skeleton rows at the height of the real rows

    The list does not jump when the content lands.

Not permitted

  • A white shimmer on a white page

    Nobody can see it, so the page looks broken, not busy.

  • A skeleton of the tabs and the toolbar

    They do not depend on the data. Show them from the start.

NextCounter

Skeleton

Draws the layout of content that is loading, so the page appears at once and content replaces each shape in place.

  • Kind: component
  • Page: https://design.werklist.com/components/skeleton
  • Version: 2.1.2

When to use it

  • A page or panel loads content of known shape
  • The body of a message or the notes of an interview load after their frame
  • A list loads its first page, such as a list of candidates

When not to use it

  • Toolbars, tabs and column headers: show them from the start
  • Work of known size: Progress bar
  • A wait inside a control: Spinner
  • A skeleton and a spinner together

Anatomy

  • fill, light: the ink mixed 22 percent into the card: color-mix(in srgb, var(--text) 22%, var(--surface-1)) (--skeleton-fill)
  • fill, dark: the ink mixed 16 percent into the card: color-mix(in srgb, var(--text) 16%, var(--surface-1)) (--skeleton-fill)
  • text line: 12 px high for Dense and Body text (13 and 14 px); corner radius 4 px (--radius-sm)
  • paragraph: lines at 80, 67 and 60 percent width, last shortest; 8 px apart (--space-2)
  • avatar: circle at the avatar size (32 px in a list row)
  • block: the size and corner radius of the component it stands for
  • row: the height of the real row (58 px for a two line list row) and its gap before the lines
  • count: as many rows as fill the visible area

States

  • loading: the whole skeleton pulses from 100 to 50 percent opacity and back over 2,000 ms, cubic-bezier(0.4, 0, 0.6, 1); all shapes in step
  • partly loaded: arrived parts replace their shapes; the rest keep pulsing
  • loaded: content replaces the shapes in place; the region is no longer busy
  • empty: gives way to the empty state
  • failed: gives way to the error and Try again, in place
  • slow: after 10 seconds the skeleton stays and a neutral inline message above it says what is still loading
  • reduced motion: no pulse

Accessibility

  • shapes are aria-hidden
  • the loading region carries aria-busy true and one role status element with a visually hidden sentence naming what loads, announced once
  • nothing in a skeleton is focusable; focus stays where it was when content arrives

Content

  • the hidden sentence names what loads: 'Loading candidates'

Tokens

  • --skeleton-fill
  • --radius-sm
  • --space-2

JSON

{
  "title": "Skeleton",
  "path": "/components/skeleton",
  "url": "https://design.werklist.com/components/skeleton",
  "text": "https://design.werklist.com/agent/components/skeleton",
  "summary": "Gray shapes in the layout of the content that is loading, so the page appears at once.",
  "version": "2.1.2",
  "kind": "component",
  "name": "Skeleton",
  "purpose": "Draws the layout of content that is loading, so the page appears at once and content replaces each shape in place.",
  "use": [
    "A page or panel loads content of known shape",
    "The body of a message or the notes of an interview load after their frame",
    "A list loads its first page, such as a list of candidates"
  ],
  "avoid": [
    "Toolbars, tabs and column headers: show them from the start",
    "Work of known size: Progress bar",
    "A wait inside a control: Spinner",
    "A skeleton and a spinner together"
  ],
  "anatomy": [
    {
      "part": "fill, light",
      "measure": "the ink mixed 22 percent into the card: color-mix(in srgb, var(--text) 22%, var(--surface-1))",
      "token": "--skeleton-fill"
    },
    {
      "part": "fill, dark",
      "measure": "the ink mixed 16 percent into the card: color-mix(in srgb, var(--text) 16%, var(--surface-1))",
      "token": "--skeleton-fill"
    },
    {
      "part": "text line",
      "measure": "12 px high for Dense and Body text (13 and 14 px); corner radius 4 px",
      "token": "--radius-sm"
    },
    {
      "part": "paragraph",
      "measure": "lines at 80, 67 and 60 percent width, last shortest; 8 px apart",
      "token": "--space-2"
    },
    {
      "part": "avatar",
      "measure": "circle at the avatar size (32 px in a list row)"
    },
    {
      "part": "block",
      "measure": "the size and corner radius of the component it stands for"
    },
    {
      "part": "row",
      "measure": "the height of the real row (58 px for a two line list row) and its gap before the lines"
    },
    {
      "part": "count",
      "measure": "as many rows as fill the visible area"
    }
  ],
  "states": [
    {
      "name": "loading",
      "change": "the whole skeleton pulses from 100 to 50 percent opacity and back over 2,000 ms, cubic-bezier(0.4, 0, 0.6, 1); all shapes in step"
    },
    {
      "name": "partly loaded",
      "change": "arrived parts replace their shapes; the rest keep pulsing"
    },
    {
      "name": "loaded",
      "change": "content replaces the shapes in place; the region is no longer busy"
    },
    {
      "name": "empty",
      "change": "gives way to the empty state"
    },
    {
      "name": "failed",
      "change": "gives way to the error and Try again, in place"
    },
    {
      "name": "slow",
      "change": "after 10 seconds the skeleton stays and a neutral inline message above it says what is still loading"
    },
    {
      "name": "reduced motion",
      "change": "no pulse"
    }
  ],
  "accessibility": [
    "shapes are aria-hidden",
    "the loading region carries aria-busy true and one role status element with a visually hidden sentence naming what loads, announced once",
    "nothing in a skeleton is focusable; focus stays where it was when content arrives"
  ],
  "content": [
    "the hidden sentence names what loads: 'Loading candidates'"
  ],
  "tokens": [
    "--skeleton-fill",
    "--radius-sm",
    "--space-2"
  ]
}

Raw file: https://design.werklist.com/agent/components/skeleton