---
title: Skeleton
description: Skeleton holds the shape of content while it loads, as a line, a circle or a block with a slow pulse. Appetite UI component for Figma, iOS and Android.
group: Components
source: https://www.appetiteui.com/docs/skeleton
dateModified: 2026-10-03
---

# Skeleton

Updated: October 3, 2026.

Skeleton holds the shape of content while it loads, so the screen does not jump when it arrives. For lists and cards it beats a spinner.

## Preview

A placeholder that holds the shape of what is coming, so the screen does not jump when the data lands. Stack three to five rows while the data loads.

Specimen: _Skeleton List Row, repeated three times. 360 x 72, circle 40, lines at gap 8, pulse 480ms.

One row is a 40 circle, a line that fills the rest and a short line under it, all breathing together. The pulse runs 480ms on a linear curve, the timing named `motion/skeleton/pulse`.

## Which shapes does Skeleton have?

Three: Line, Circle and Block, and nothing else. No text, no icons, no content of any kind.

Specimen: Line, Circle and Block. 200 x 12, 40 x 40, 200 x 120.

Line and Circle are both radius full, so a 12 tall line is a stadium and a 40 square is a disc. Block is the only one on a real radius token, `dimensions/radius/lg` 12, so it reads as a card.

Every size here is a default meant to be resized on the instance.

## How does Skeleton pulse?

Two variants make one breath: Pulse 1 is the stronger gray and Pulse 2 the weaker. Held still here so you can see both.

Specimen: Pulse 1 then Pulse 2, at Line, Circle and Block. bg/sub-200 to bg/soft-100.

Pulse is a timeline, not a state. Both ends move the same way in either theme: light runs #D1D1D6 down to #E9E9EA, dark #636366 down to #48484A, so it always breathes from more contrast to less.

In code, animate between the two colors and alternate. When the reader prefers reduced motion, show Pulse 1 without the animation.

## How does Skeleton differ on iOS and Material 3?

Neither platform draws a skeleton in the Figma file. SwiftUI offers the redacted modifier with the placeholder reason, and Material 3 leaves loading to its progress indicators.

| Appetite UI component | iOS counterpart | Material 3 counterpart | What differs |
| --- | --- | --- | --- |
| Skeleton, Line 200 x 12, Circle 40, Block 200 x 120 | SwiftUI redacted(reason: .placeholder), no Figma component | No counterpart, progress indicators only | Appetite UI draws the shapes you place. SwiftUI blanks the real content in place. Neither platform defines a pulse |

A skeleton has no interaction, so no touch target applies. It carries no text, so announce the loading state once on the container, for example with a label such as Loading, and keep the gray shapes out of the reading order.

The shapes sit on `bg/sub-200`, which is too faint to carry contrast, so they signal shape and nothing else. See [Progress](https://www.appetiteui.com/docs/progress) for a spinner.

## Properties

Two axes, six variants, and one ready made row beside them.

| Property | Type | Default |
| --- | --- | --- |
| `Type` | Block \| Circle \| Line | Block |
| `Pulse` | 1 \| 2 | 1 |

| Component | Variants | Size |
| --- | --- | --- |
| `Skeleton` | 6, Type crossed with Pulse | 200 x 12, 40 x 40, 200 x 120 |
| `_Skeleton List Row` | One component, no properties | 360 x 72 at radius 4 |

Check the ready made row against the list it stands in for before you use it as is. It is 360 by 72 at radius 4 with a 16 gap, a circle at radius full and 272 of content, and none of that is bound to the List component.

A stack of these holds the shape of the list only if you resize the instance to match.

## Tokens

Two grays and one piece of motion. That is the whole component.

| Token | Light | Dark | Applied to |
| --- | --- | --- | --- |
| `bg/sub-200` | #D1D1D6 | #636366 | Pulse 1, the stronger end of the breath |
| `bg/soft-100` | #E9E9EA | #48484A | Pulse 2, the weaker end |

| Motion token | Resolves to | Value |
| --- | --- | --- |
| `motion/skeleton/pulse/duration` | motion/duration/slower | 480ms |
| `motion/skeleton/pulse/easing` | motion/easing/linear | cubic-bezier(0, 0, 1, 1) |

Linear is right here and unusual everywhere else in the system: a breath that eases reads as a thing being tapped. No semantic loading token exists: the two grays are the background tokens used for dividers and empty states, so a skeleton on `bg/soft-100` loses one end of its pulse.

## Guidelines

- Do: Repeat the row and let it hold the shape of the list underneath. Three to five is the range to use, and nothing jumps when the data arrives.

- Avoid: Do not stand one gray block in for a list. It promises a shape the content will not have, so the layout jumps anyway and it says less than a spinner would.

Reach for it when the layout is known and the wait is short: lists, cards, a profile header. Match the skeleton to the real thing and resize the instance rather than accept the defaults.

Do not skeleton a whole screen, because a page of gray is harder to read than one spinner. Cap it: past about ten seconds, switch to an error state.

## In the screens and the glossary

- Glossary: [Skeleton screen](https://www.appetiteui.com/academy/glossary/skeleton-screen)
