Skeleton
A placeholder shape shown while content loads.
<template> <Skeleton class="h-4 w-32" /></template>Props
| Name | Type | Default |
|---|---|---|
| ...props | USkeleton props (as, class) | - |
Slots
| Name | Description |
|---|---|
| default | Rarely used - USkeleton is normally self-closing. |
Guidelines
Do
- Give it the same
h-*/w-*(androunded-fullfor circles) as the real content it's standing in for, so the layout doesn't shift when the content arrives. - Compose several into a layout that mirrors the real one (e.g. an avatar circle next to two stacked text lines) rather than one large placeholder block.
- Use it for content that takes more than a couple seconds to load - a list, a table, a chart.
- Reach for it specifically where a component is gated on an async fetch/query with a known shape - not as a blanket default applied to every page regardless of whether anything on it is actually loading.
- When skeletonizing a whole page or section, mirror the actual content structure (cards, rows, chart axes) rather than just the header/chrome with an empty body - a frame-only skeleton reads as broken if the wait runs long.
- Thread one
loadingflag down from the page's query state so each component swaps its own real markup for shape-matched Skeletons, rather than a page-level component reaching in to know every child's internal layout. - For several Skeletons in one region (a table's loading rows, a list), wrap the region in one shared
aria-hidden+ externalrole="status"announcement instead of leaving each Skeleton's own live region active - seetable.vue'sloadingStyle="skeleton"for the pattern; N duplicate announcements is worse than one.
Don't
- Don't use it for a short, indeterminate wait with no shape to mirror (an auth redirect, a button's pending state) - use
Spinnerinstead. - Don't hardcode a shape here as a prop - pass it via
classper instance, since it varies with whatever content each call site is replacing. - Don't skeletonize static chrome that never changes shape (dividers, section icons, nav) - reserve it for elements that actually signal "content is arriving here."
- Don't leave a skeleton state up indefinitely - cap it with a timeout/error fallback rather than trusting the query to always resolve.
- Don't skeletonize content that's already available synchronously (static copy, layout chrome, props passed in from a parent that already has the data) - there's no wait to mask, so it's just a placeholder flash before the real content anyway.
- Don't skeletonize a page uniformly just because some section on it fetches - only the sections actually gated on that fetch need it; a page isn't "skeleton" or "not skeleton" as a whole.