A skeleton is the shape of the content, drawn before the content arrives, so the layout does not move when it does. Draw one when you know that shape and the wait is long enough to see.
- A first load of a layout you know in advance: a profile card, a list row, a table body, a dashboard tile.
- A wait long enough to notice, about a third of a second and up, that you cannot put a number on.
- A surface where the placeholder can sit in the same box the content will land in, with the same padding and the same container.
What to reach for instead.
- Nothing at all under about 200 milliseconds. A placeholder that appears and disappears inside a quarter of a second reads as a flicker, which is worse than the wait it hides.
- Spin when the wait is short, or when you do not know the shape: a dialog opening, a search re-running, a save.
- Progress when the wait is measurable: an upload, an export, a job with steps. A skeleton cannot say how far along anything is.
- The control's own busy state for something the reader just did. A Button with loading already says it, next to the thing that caused it.
- Empty or Result for a wait that ended with nothing, or ended badly. A skeleton that never resolves looks like a broken page.
- Table's own loading when the surface is a Table. It shows a spinner over the rows. Build a grid of Skeleton.Input bars only for a data grid that is not a Table.
A refresh of content already on screen is usually not a skeleton either. Replacing what the reader is reading with gray bars loses their place. Dim the old content and leave it there.