# Skeleton

A placeholder shown while the real content loads: lines of text, a title, a label, an icon, an avatar, a badge, a button, an input, a media box, a list item, a card or a table row, or the shape of any component it wraps.

Block: `smbk-skeleton`. Status: experimental. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `shape` | `text`, `title`, `label`, `paragraph`, `icon`, `avatar`, `badge`, `button`, `input`, `media`, `list-item`, `card`, `table-row`, `component` | `text` | Figma's thirteen shapes, for content that is not a component; component, not in Figma, for anything else: the skeleton wraps the real component, which keeps its size, place and corners and draws the skeleton instead of itself until it has loaded. |
| `size` | `sm`, `md`, `lg` | `md` | The size of the content it stands in for: the text styles a step smaller or larger, and the icon, avatar, badge, button, input and table row at their small, medium or large size. The media shape and a wrapped component take their size from what they stand for. |
| `loaded` | on / off | off | The wrapped component has loaded and shows as itself. Only the component shape has something to show. |

Parts: `smbk-skeleton__line`, `smbk-skeleton__text`

## Markup

```html
<span aria-hidden="true" class="smbk-skeleton smbk-skeleton--shape-text smbk-skeleton--size-md">
  <span class="smbk-skeleton__line"></span>
  <span class="smbk-skeleton__line"></span>
  <span class="smbk-skeleton__line"></span>
</span>
```

## React

```tsx
import { Skeleton } from './design-system/components/skeleton/skeleton.js';

<Skeleton shape="text" size="md" lines={3} />
```

## Stylesheet

```css
/* smbk-skeleton — Skeleton. Figma "Skeleton" 915:9 (Shape × Size, 39 variants).
 * Widths in Figma are example widths and not bound; a skeleton takes the width
 * it is given. The text, paragraph, title and label shapes take the room of
 * the text they stand in for: each takes its preset's face, size and line
 * height (body, heading and label, one step per size), so one line is one
 * line height tall, with a bar as tall as the text's capitals drawn in its
 * middle, and the lines of a paragraph (line parts, written by the wrapper for
 * more than one line) stand one line height apart, so a skeleton paragraph is
 * exactly as tall as the paragraph it stands in for. The icon, avatar, badge,
 * button and input match those components' sizes at each size; the media
 * shape takes the proportion of the media it replaces, set by the wrapper, 16:9
 * by default. The list item, card and table row shapes are built from the
 * other shapes, so each bar inside them is a line of the text it stands for.
 * While it waits, a lighter band sweeps across it a few times with the
 * loading motion, then it rests (rules at the end). */

.smbk-skeleton {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 1lh;
  padding-block: calc((1lh - 1cap) / 2);
  background-clip: content-box;
  border-radius: var(--smbk-radius-control-xs);
  background-color: var(--smbk-color-surface-skeleton);
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
}

/* ---- size: the text shape's body style, one step per size ---- */

.smbk-skeleton--size-sm {
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
}

.smbk-skeleton--size-lg {
  font-family: var(--smbk-typography-body-lg-font-family);
  font-size: var(--smbk-typography-body-lg-font-size);
  font-weight: var(--smbk-typography-body-lg-font-weight);
  line-height: var(--smbk-typography-body-lg-line-height);
}

/* ---- title and label: the heading and label styles, one step per size ---- */

.smbk-skeleton--shape-title {
  border-radius: var(--smbk-radius-control-sm);
  font-family: var(--smbk-typography-heading-md-font-family);
  font-size: var(--smbk-typography-heading-md-font-size);
  font-weight: var(--smbk-typography-heading-md-font-weight);
  line-height: var(--smbk-typography-heading-md-line-height);
}

.smbk-skeleton--shape-title.smbk-skeleton--size-sm {
  font-family: var(--smbk-typography-heading-sm-font-family);
  font-size: var(--smbk-typography-heading-sm-font-size);
  font-weight: var(--smbk-typography-heading-sm-font-weight);
  line-height: var(--smbk-typography-heading-sm-line-height);
}

.smbk-skeleton--shape-title.smbk-skeleton--size-lg {
  font-family: var(--smbk-typography-heading-lg-font-family);
  font-size: var(--smbk-typography-heading-lg-font-size);
  font-weight: var(--smbk-typography-heading-lg-font-weight);
  line-height: var(--smbk-typography-heading-lg-line-height);
}

.smbk-skeleton--shape-label {
  font-family: var(--smbk-typography-label-md-font-family);
  font-size: var(--smbk-typography-label-md-font-size);
  font-weight: var(--smbk-typography-label-md-font-weight);
  line-height: var(--smbk-typography-label-md-line-height);
}

.smbk-skeleton--shape-label.smbk-skeleton--size-sm {
  font-family: var(--smbk-typography-label-sm-font-family);
  font-size: var(--smbk-typography-label-sm-font-size);
  font-weight: var(--smbk-typography-label-sm-font-weight);
  line-height: var(--smbk-typography-label-sm-line-height);
}

.smbk-skeleton--shape-label.smbk-skeleton--size-lg {
  font-family: var(--smbk-typography-label-lg-font-family);
  font-size: var(--smbk-typography-label-lg-font-size);
  font-weight: var(--smbk-typography-label-lg-font-weight);
  line-height: var(--smbk-typography-label-lg-line-height);
}

/* A paragraph: the block holds its lines, one line height apart, and draws nothing itself. */
.smbk-skeleton:has(> .smbk-skeleton__line) {
  display: flex;
  flex-direction: column;
  gap: calc(1lh - 1cap);
  height: auto;
  padding-block: calc((1lh - 1cap) / 2);
  background: none;
  animation: none;
}

.smbk-skeleton__line {
  display: block;
  height: 1cap;
  border-radius: inherit;
  background-color: var(--smbk-color-surface-skeleton);
}

/* The paragraph shape ends on a shorter line, as a paragraph does. */
.smbk-skeleton--shape-paragraph > .smbk-skeleton__line:last-child:not(:first-child) {
  width: 60%;
}

/* ---- the solid shapes: a block of the skeleton's color, no text box ---- */

.smbk-skeleton:is(
  .smbk-skeleton--shape-icon,
  .smbk-skeleton--shape-avatar,
  .smbk-skeleton--shape-badge,
  .smbk-skeleton--shape-button,
  .smbk-skeleton--shape-input,
  .smbk-skeleton--shape-media
) {
  padding-block: 0;
  background-clip: border-box;
}

.smbk-skeleton--shape-icon {
  width: var(--smbk-size-icon-md);
  height: var(--smbk-size-icon-md);
}

.smbk-skeleton--shape-icon.smbk-skeleton--size-sm {
  width: var(--smbk-size-icon-sm);
  height: var(--smbk-size-icon-sm);
}

.smbk-skeleton--shape-icon.smbk-skeleton--size-lg {
  width: var(--smbk-size-icon-lg);
  height: var(--smbk-size-icon-lg);
}

/* The avatar's own steps: the avatar does not change with density, so neither does its skeleton. */
.smbk-skeleton--shape-avatar {
  flex-shrink: 0;
  width: var(--smbk-size-40);
  height: var(--smbk-size-40);
  border-radius: var(--smbk-radius-full);
}

.smbk-skeleton--shape-avatar.smbk-skeleton--size-sm {
  width: var(--smbk-size-32);
  height: var(--smbk-size-32);
}

.smbk-skeleton--shape-avatar.smbk-skeleton--size-lg {
  width: var(--smbk-size-48);
  height: var(--smbk-size-48);
}

/* The badge's pill heights; the widths are Figma's examples, a short word's room. */
.smbk-skeleton--shape-badge {
  width: var(--smbk-size-56);
  height: var(--smbk-size-mark-md);
  border-radius: var(--smbk-radius-full);
}

.smbk-skeleton--shape-badge.smbk-skeleton--size-sm {
  width: var(--smbk-size-48);
  height: var(--smbk-size-mark-sm);
}

.smbk-skeleton--shape-badge.smbk-skeleton--size-lg {
  width: var(--smbk-size-64);
  height: var(--smbk-size-mark-lg);
}

/* The button's control heights and corners; three times as wide as it is tall, a short label's room. */
.smbk-skeleton--shape-button {
  width: auto;
  max-width: 100%;
  aspect-ratio: 3 / 1;
  height: var(--smbk-size-control-md);
  border-radius: var(--smbk-radius-control-sm);
}

.smbk-skeleton--shape-button.smbk-skeleton--size-sm {
  height: var(--smbk-size-control-sm);
}

.smbk-skeleton--shape-button.smbk-skeleton--size-lg {
  height: var(--smbk-size-control-lg);
  border-radius: var(--smbk-radius-control-md);
}

/* The input's control heights and corners, as wide as it is given. */
.smbk-skeleton--shape-input {
  height: var(--smbk-size-control-md);
  border-radius: var(--smbk-radius-control-sm);
}

.smbk-skeleton--shape-input.smbk-skeleton--size-sm {
  height: var(--smbk-size-control-sm);
}

.smbk-skeleton--shape-input.smbk-skeleton--size-lg {
  height: var(--smbk-size-control-lg);
  border-radius: var(--smbk-radius-control-md);
}

.smbk-skeleton--shape-media {
  height: auto;
  aspect-ratio: 16 / 9;
  border-radius: var(--smbk-radius-container-sm);
}

/* ---- the blocks: list item, card and table row, laid out from the other shapes ----
   The block draws nothing of the skeleton's itself (the card draws its frame); each shape inside
   draws and sweeps on its own. */

/* No more weight than one class, so each block's own rules below set its height, padding and corner. */
.smbk-skeleton:where(.smbk-skeleton--shape-list-item, .smbk-skeleton--shape-card, .smbk-skeleton--shape-table-row) {
  height: auto;
  padding-block: 0;
  border-radius: 0;
  background: none;
  animation: none;
}

.smbk-skeleton__text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-text-label);
  min-width: 0;
}

/* The list item: an avatar beside a label line over a shorter text line, as a person or a file in a list. */
.smbk-skeleton--shape-list-item {
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-lg);
}

.smbk-skeleton--shape-list-item .smbk-skeleton__text > :first-child {
  width: 50%;
}

/* The card: its frame, then a 16:9 picture, a title line and two text lines, the second shorter. */
.smbk-skeleton--shape-card {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: var(--smbk-space-stack-md);
  padding: var(--smbk-space-inset-lg);
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
  border-radius: var(--smbk-radius-container-md);
}

.smbk-skeleton--shape-card.smbk-skeleton--size-sm {
  padding: var(--smbk-space-inset-md);
}

.smbk-skeleton--shape-card > .smbk-skeleton--shape-title,
.smbk-skeleton--shape-card > .smbk-skeleton--shape-text:not(.smbk-skeleton--shape-paragraph) {
  width: 66%;
}

/* The table row: the row's control height with its rule along the foot, a selection box, then one
   label line per column, the columns sharing the row. */
.smbk-skeleton--shape-table-row {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-2xl);
  height: var(--smbk-size-control-md);
  padding-inline: var(--smbk-space-inset-md);
  border-bottom: var(--smbk-border-width-hairline) solid var(--smbk-color-border-subtle);
}

.smbk-skeleton--shape-table-row.smbk-skeleton--size-sm {
  height: var(--smbk-size-control-sm);
}

.smbk-skeleton--shape-table-row.smbk-skeleton--size-lg {
  height: var(--smbk-size-control-lg);
}

.smbk-skeleton--shape-table-row > .smbk-skeleton--shape-icon {
  flex-shrink: 0;
  width: var(--smbk-size-icon-sm);
  height: var(--smbk-size-icon-sm);
}

.smbk-skeleton--shape-table-row > .smbk-skeleton--shape-label {
  flex: 1 1 0;
  min-width: 0;
}

/* ---- motion: the sweep ----
   A band of the default surface, half the skeleton's width, fading in and out at its edges, crosses
   the skeleton from just past its start edge to just past its end edge with the loading purpose,
   three times, then stops: three sweeps of the loading duration end inside five seconds, after
   which nothing may move on its own beside other content. The skeleton's color is see-through,
   and the opaque band over it reads on every surface. Both ends lie
   outside the skeleton, so a sweep starts and ends on the plain shape, and where motion is stopped
   (reduced motion, the rendered pictures, the end of the third sweep) the skeleton is the plain
   shape at rest. The distances are the skeleton's own width: half for the band, and the positions
   that put it just outside either edge. */

.smbk-skeleton,
.smbk-skeleton__line {
  background-image: linear-gradient(to right, transparent, var(--smbk-color-surface-default), transparent);
  background-position: -100% 0;
  background-repeat: no-repeat;
  background-size: 50% 100%;
  animation: smbk-skeleton-sweep var(--smbk-motion-loading-duration) var(--smbk-motion-loading-easing) 3;
}

@keyframes smbk-skeleton-sweep {
  to {
    background-position: 200% 0;
  }
}

/* ---- component: the shape of the component it wraps ----
   The skeleton draws no box of its own: the wrapped component keeps its size, place and corners and
   draws the skeleton's surface and sweep instead of its own look, its words, icons and pictures
   hidden but still holding their room, until the loaded flag is set. Its edge keeps its width but is
   clear, and the band crosses it too (measured from the border box). It must win over every state the
   wrapped component draws, whatever its own rules' weight, so these rules are marked important; the
   band's position is not, because the sweep moves it. */

.smbk-skeleton--shape-component {
  display: contents;
  font: inherit;
  animation: none;
}

.smbk-skeleton--shape-component:not(.smbk-skeleton--loaded) > * {
  border-color: transparent !important;
  outline-color: transparent !important;
  background-color: var(--smbk-color-surface-skeleton) !important;
  background-image: linear-gradient(to right, transparent, var(--smbk-color-surface-default), transparent) !important;
  background-origin: border-box !important;
  background-position: -100% 0;
  background-repeat: no-repeat !important;
  background-size: 50% 100% !important;
  box-shadow: none !important;
  color: transparent !important;
  pointer-events: none;
  animation: smbk-skeleton-sweep var(--smbk-motion-loading-duration) var(--smbk-motion-loading-easing) 3 !important;
}

.smbk-skeleton--shape-component:not(.smbk-skeleton--loaded) > * * {
  visibility: hidden !important;
}

@media (prefers-reduced-motion: reduce) {
  .smbk-skeleton,
  .smbk-skeleton__line {
    animation: none;
  }

  .smbk-skeleton--shape-component:not(.smbk-skeleton--loaded) > * {
    background-image: none !important;
    animation: none !important;
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  /* The shapes are backgrounds, which forced colors drops; each is outlined in GrayText inside
     its own box instead, so it shows as a placeholder without taking room. A paragraph outlines
     its lines, and a list item or a table row the shapes inside it, not itself; a card keeps its
     frame. A wrapped component keeps its own shape, its edge in GrayText. */
  .smbk-skeleton:not(:has(> .smbk-skeleton__line), .smbk-skeleton--shape-list-item, .smbk-skeleton--shape-card, .smbk-skeleton--shape-table-row, .smbk-skeleton--shape-component),
  .smbk-skeleton__line {
    outline: var(--smbk-border-width-hairline) solid GrayText;
    outline-offset: calc(-1 * var(--smbk-border-width-hairline));
  }

  .smbk-skeleton--shape-component:not(.smbk-skeleton--loaded) > * {
    border-color: GrayText !important;
  }
}
```

## Tokens it reads

```json
{
  "radius": {
    "control": {
      "xs": {
        "$type": "dimension",
        "$value": "{{radius.2}}",
        "$description": "Corners of controls, extra small."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.4}}",
        "$description": "Corners of controls, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{radius.6}}",
        "$description": "Corners of controls, medium."
      }
    },
    "full": {
      "$type": "dimension",
      "$value": "{{radius.9999}}",
      "$description": "Capsule: pills, chips, switches."
    },
    "container": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.8}}",
        "$description": "Corners of cards, panels and dialogs, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{radius.12}}",
        "$description": "Corners of cards, panels and dialogs, medium."
      }
    }
  },
  "color": {
    "surface": {
      "skeleton": {
        "$type": "color",
        "$value": "{{color.alpha.grey-800.10}}",
        "$description": "Skeleton shapes: translucent, so they show by the same step on the canvas, default, raised and sunken surfaces in both themes."
      },
      "default": {
        "$type": "color",
        "$value": "{{color.grey.100}}",
        "$description": "Standard surface: cards, panels, the header."
      }
    },
    "border": {
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      }
    }
  },
  "typography": {
    "body-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "body-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "20px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "heading-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "28px",
          "max": "40px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "heading-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "24px",
          "max": "32px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "heading-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Funnel Display, Funnel Display Fallback, system-ui, sans-serif",
        "fontSize": {
          "min": "32px",
          "max": "48px"
        },
        "fontWeight": 400,
        "lineHeight": 1.2,
        "letterSpacing": "0px"
      }
    },
    "label-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label MD: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    },
    "label-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "12px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label SM: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    },
    "label-lg": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 500,
        "lineHeight": 1.4,
        "letterSpacing": "0px"
      },
      "$description": "Label LG: labels on controls and short UI text (buttons, chips, tabs, menu headings). Geist Medium, sentence case. Code and keys use Code SM and Code MD."
    }
  },
  "size": {
    "32": {
      "$type": "dimension",
      "$value": "32px",
      "$description": "Raw size step, 32 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "40": {
      "$type": "dimension",
      "$value": "40px",
      "$description": "Raw size step, 40 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "48": {
      "$type": "dimension",
      "$value": "48px",
      "$description": "Raw size step, 48 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "56": {
      "$type": "dimension",
      "$value": "56px",
      "$description": "Raw size step, 56 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "64": {
      "$type": "dimension",
      "$value": "64px",
      "$description": "Raw size step, 64 px. Only for fixed sizes inside a component; semantic sizes (size/control, icon, mark) point here."
    },
    "icon": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Icon box, medium."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.16}}",
        "$description": "Icon box, small."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Icon box, large."
      }
    },
    "mark": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Small fixed marks inside components (dots, bars, keys), medium."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.16}}",
        "$description": "Small fixed marks inside components (dots, bars, keys), small."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Small fixed marks inside components (dots, bars, keys), large."
      }
    },
    "control": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.40}}",
        "$description": "Height of a medium control, where controls insert by default: 40 comfortable, 32 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{size.32}}",
        "$description": "Height of a small control: 32 comfortable, 28 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{size.48}}",
        "$description": "Height of a large control: 48 comfortable, 40 compact. A page may use it for its main call to action; apps use md. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    }
  },
  "space": {
    "text": {
      "label": {
        "$type": "dimension",
        "$value": "{{space.0}}",
        "$description": "Between a label or title and the line of supporting text right under it, inside a component. None: untrimmed line height already spaces the two lines."
      }
    },
    "inline": {
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things side by side, large."
      },
      "2xl": {
        "$type": "dimension",
        "$value": "{{space.24}}",
        "$description": "Gap between things side by side, 2× large."
      }
    },
    "stack": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      }
    },
    "inset": {
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Padding inside a component, large."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Padding inside a component, medium."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "motion": {
    "loading": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.1600}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.cubic-in-out}}"
      }
    }
  }
}
```

## Purpose

A gray shape standing in for content that has not arrived yet, so the layout holds still and the person knows where things will appear. Its shape is the shape of what will come, since a skeleton's geometry must match the likely content: one of thirteen shapes for content that is not a component, each in a small, medium or large size, or, wrapped round a real component, that component's own shape.

## Anatomy

- A shape: one element. The text, title and label shapes are one line of their text: a bar as tall as its capitals, in the middle of one line height. Given more than one line, and always for the paragraph shape, the element holds a line part per line, one line height apart, and draws nothing itself; a paragraph's last line is shorter.
- The icon, avatar, badge, button and input shapes are solid blocks at those components' sizes, and the media shape a block at the proportion of the picture it replaces, 16:9 by default.
- A block: the list item, the card and the table row, one element holding other shapes. The list item is an avatar beside a label line over a smaller text line, in a text part. The card is a framed block with a 16:9 media shape, a title line and two text lines. The table row is a row at the control height with a rule along its foot, a small square for the selection box and a label line per column.
- A wrapper: one element round the component it stands in for, drawing no box of its own. The component inside keeps its size, place and corners and draws the skeleton instead of itself until it has loaded.

## When to use

- While a list, a card or a profile loads, in place of each text line, title, avatar, picture and button it will show.
- The list item, card and table row shapes for a whole row or card at once, on a page with no component to wrap.
- The size that matches the content: small, medium or large text, and the small, medium or large control.
- Wrapped round any component that waits for its data (an input filled from the server, a badge whose status is still coming, a button that cannot act yet), so the skeleton has that component's exact shape at whatever size it is.

## When not to use

- For a delay shorter than a moment. A flash of skeleton is worse than nothing.
- As a spinner for an action. Use the [progress bar](progress.md).

## Behavior

As it appears, a soft band sweeps across it from the start edge to the end edge, easing in and out, three times, so the reader can see the page is working; then it rests as the plain shape, since nothing may move on its own for more than five seconds beside other content. The band begins and ends outside the skeleton, so each sweep starts on the plain shape. Skeletons side by side of the same width sweep together. Under reduced motion it does not move: it is the plain shape, which says the same thing. A wait long enough to outlast the sweep is better shown with a progress bar.

## Accessibility
### What it does for you

- Skeletons are hidden from assistive technology.
- A wrapped component is also out of reach while it waits: the keyboard skips it and assistive technology does not see it, so nobody can press a button that cannot act yet.
- Once loaded, a wrapped component is reachable again. It never left the page, so it keeps its state and focus can move to it at once.
- The React wrapper gives the wrapper `inert` and `aria-hidden="true"` through its `loaded` flag, and removes both once the component has loaded.

### What you must do

- Make the region that shows skeletons announce that it is loading, and announce again when the content arrives.
- In plain markup, give the wrapper `inert` and `aria-hidden="true"` while it waits, and remove both once the component has loaded.

### How to test

- Tab through the page while a wrapped component waits: focus skips it. Once it has loaded, Tab reaches it.
- With a screen reader, the skeletons are not read, the region says it is loading, and says so again when the content arrives.
- With reduced motion on, the skeleton does not move.

## Usage rules

### Do

- Match shapes to the content: a title skeleton for a title, an avatar skeleton for an avatar.
- Wrap the real component when there is one: its own shape fits better than any block.
- Give a text skeleton the number of lines the text will have, so it is as tall as the paragraph that replaces it.
- Give a skeleton the width its content will have, and a media skeleton the ratio of the picture or video it replaces.

### Don't

- Keep skeletons on screen after an error. Show the error.
