Skeletons
Shape quiet, theme-aware loading placeholders with text, circle, media, sizing, and motion-safe animation primitives.
Loading skeletons
Skeletons reserve the approximate shape of content while a view loads. Start with .bs-skeleton, add one shape modifier, and optionally add a pulse or wave animation. The component has no JavaScript controller: your application decides when to render the placeholders and when to replace them with real content.
Text placeholders
Use several text skeletons with varied widths to suggest the line lengths of the final copy. Widths can be set per element with --bs-skeleton-width; avoid making every line identical.
<div class="bs-stack bs-gap-2" aria-hidden="true">
<span class="bs-skeleton bs-skeleton-text bs-skeleton-pulse"
style="--bs-skeleton-width: 42%"></span>
<span class="bs-skeleton bs-skeleton-text bs-skeleton-pulse"></span>
<span class="bs-skeleton bs-skeleton-text bs-skeleton-pulse"
style="--bs-skeleton-width: 76%"></span>
</div>Shapes and sizes
Choose shapes based on the content they stand in for. Text uses a compact block size, media reserves a ten-rem block by default, and circles need matching width and height values so their aspect ratio resolves to a true circle. The small and large modifiers provide convenient fixed heights for controls, labels, and emphasized blocks.
<span class="bs-skeleton bs-skeleton-circle bs-skeleton-wave"
style="--bs-skeleton-width: 4rem; --bs-skeleton-height: 4rem"></span>
<span class="bs-skeleton bs-skeleton-media bs-skeleton-wave"></span>
<span class="bs-skeleton bs-skeleton-sm bs-skeleton-wave"></span>
<span class="bs-skeleton bs-skeleton-lg bs-skeleton-wave"></span>Card loading state
Match the stable structure of the final card so the page does not jump when its data arrives. Put aria-busy="true" on the region being updated, expose one meaningful loading message, and hide the purely visual placeholder group from assistive technology.
Loading project summary…
<p class="bs-sr-only" role="status">Loading project summary…</p>
<article class="bs-card" aria-busy="true" aria-label="Project summary">
<div aria-hidden="true">
<div class="bs-skeleton bs-skeleton-media bs-skeleton-wave"></div>
<div class="bs-card-content bs-stack bs-gap-3">
<div class="bs-skeleton bs-skeleton-lg bs-skeleton-wave"
style="--bs-skeleton-width: 48%"></div>
<div class="bs-skeleton bs-skeleton-text bs-skeleton-wave"></div>
<div class="bs-skeleton bs-skeleton-text bs-skeleton-wave"
style="--bs-skeleton-width: 72%"></div>
</div>
</div>
</article>List loading state
Repeat one placeholder pattern when the final view contains repeated records. Keep the real section heading available as context, then hide only the placeholder rows.
Loading recent activity…
Recent activity
<p class="bs-sr-only" role="status">Loading recent activity…</p>
<section aria-busy="true" aria-labelledby="skeletons-activity-example-title">
<h2 id="skeletons-activity-example-title">Recent activity</h2>
<div class="bs-stack bs-gap-4" aria-hidden="true">
<div class="bs-flex bs-items-center bs-gap-3">
<span class="bs-skeleton bs-skeleton-circle bs-skeleton-pulse"
style="--bs-skeleton-width: 3rem; --bs-skeleton-height: 3rem"></span>
<span class="bs-stack bs-gap-2 bs-w-full">
<span class="bs-skeleton bs-skeleton-text bs-skeleton-pulse"
style="--bs-skeleton-width: 54%"></span>
<span class="bs-skeleton bs-skeleton-sm bs-skeleton-pulse"
style="--bs-skeleton-width: 78%"></span>
</span>
</div>
<!-- Repeat rows to match the expected result count. -->
</div>
</section>Custom dimensions
The base component reads its logical width and height from custom properties. Override either value inline for one placeholder or in application CSS for a reusable shape. Percent widths adapt to their container; fixed block sizes are useful for avatars and media with known dimensions.
.product-thumbnail-skeleton {
--bs-skeleton-width: 6rem;
--bs-skeleton-height: 6rem;
}Motion and themes
Use .bs-skeleton-pulse for a restrained opacity change or .bs-skeleton-wave for a directional highlight. One animation is enough to communicate that a view is waiting. Skeleton surfaces and wave highlights use semantic Boobstrap color tokens, so they follow light, dark, and scoped themes without component-specific overrides.
Both animations stop automatically under prefers-reduced-motion: reduce. The wave highlight is removed as well, leaving a stable placeholder that preserves layout. Do not add application animation rules that override this fallback.
Represent the result
Approximate the final content's geometry and count. Skeletons should reduce layout shift, not reproduce every visual detail.
Own the loading state
Put aria-busy="true" on the region receiving new content. Remove it—or set it to false—when the replacement is complete.
Hide decoration
Wrap placeholder shapes in aria-hidden="true". If users need an immediate announcement, expose one concise status message outside both the hidden group and the busy region.
Choose the right component
Skeletons communicate an unmeasured wait and must not use role="progressbar". Use the progress component when a real value or task progression matters.
Skeleton API
| Class or property | Purpose | Default |
|---|---|---|
.bs-skeleton | Base placeholder surface, clipping, radius, and logical dimensions. | Full width by one em |
.bs-skeleton-text | Text-line shape with compact height and vertical breathing room. | 0.8em high |
.bs-skeleton-circle | Pill-radius placeholder for avatars and circular media. Set equal width and height. | Uses base dimensions |
.bs-skeleton-media | Large rectangular image or media placeholder. | 10rem high |
.bs-skeleton-sm | Compact fixed-height placeholder. | 0.625rem high |
.bs-skeleton-lg | Emphasized fixed-height placeholder. | 2rem high |
.bs-skeleton-pulse | Repeating opacity animation with a reduced-motion fallback. | 1.4s |
.bs-skeleton-wave | Moving highlight animation with a reduced-motion fallback. | 1.5s |
--bs-skeleton-width | Logical inline size for custom composition. | 100% |
--bs-skeleton-height | Logical block size for custom composition. | 1em |