Hacktron Design System

Skeleton

A placeholder shape shown while content loads.

<template>  <Skeleton class="h-4 w-32" /></template>

Props

Name Type Default
...propsUSkeleton props (as, class)-

Slots

Name Description
defaultRarely used - USkeleton is normally self-closing.

Guidelines

Do

  • Give it the same h-*/w-* (and rounded-full for 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 loading flag 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 + external role="status" announcement instead of leaving each Skeleton's own live region active - see table.vue's loadingStyle="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 Spinner instead.
  • Don't hardcode a shape here as a prop - pass it via class per 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.