Skip to content

Skeleton

WxSkeleton holds the shape of what is coming, so the page does not jump when it lands.

Rows, with a face and a heading
A shape of your own

Usage

vue
<template>
  <wx-skeleton :loading="loading" avatar title :rows="3">
    <article>{{ post.body }}</article>
  </wx-skeleton>
</template>

The same markup covers both states: while loading the placeholder shows, and once it is off the default slot renders. That is one v-if the caller does not write, and — more to the point — one place where the two cannot drift apart.

A shape, not a spinner

A spinner says something is happening. A skeleton says something is happening, and it will be this shape — so the reader's eye is already where the content will be, and nothing moves when it arrives. Use a spinner for work with no shape to promise (a save, a delete); use this for content.

Shape it like the thing

rows, avatar and title cover a paragraph with a face beside it. Anything else goes in #template, built from WxSkeletonItem:

vue
<wx-skeleton :loading="loading">
  <template #template>
    <wx-skeleton-item variant="image" />
    <wx-skeleton-item variant="title" />
    <wx-skeleton-item variant="text" width="70%" />
  </template>

  <product-card :product="product" />
</wx-skeleton>

The placeholder should be roughly the size of the real thing. One that is much shorter still lets the page jump, which is the whole thing it was there to prevent.

Do not flash

delay holds the placeholder back for a few hundred milliseconds. A request that answers in 80ms should show nothing at all — a skeleton that appears and vanishes within one frame reads as a glitch, not as progress.

vue
<wx-skeleton :loading="loading" :delay="200">…</wx-skeleton>

The delay is only ever on showing. Content that has arrived is shown at once.

Skeleton

PropTypeDefaultDescription
loadingbooleantrueWhether the placeholder is showing
rowsnumber3Lines of text to stand in for
avatarbooleanfalseAdds a circle beside the lines
titlebooleanfalseDraws the first line heavier
animatedbooleantrueThe travelling sheen
delaynumber0How long before it appears, in milliseconds

Slots: default — the real thing; template — a placeholder shaped like it.

SkeletonItem

PropTypeDefaultDescription
variant'text' | 'title' | 'circle' | 'square' | 'image' | 'button''text'What it stands in for
widthstring | numberAny CSS length
heightstring | numberAny CSS length
animatedbooleantrueThe sheen

Accessibility

The placeholder carries aria-busy and aria-live="polite", and every block inside it is aria-hidden — there is nothing to read in a grey rectangle. Reduced motion stops the sheen and keeps the blocks: a still placeholder still says not yet.

Released under the MIT License.