Bootstrap 5 Skeleton Loader
A skeleton loader is a placeholder UI that mimics the shape and layout of real content whilst data is loading, reducing perceived wait time and preventing layout shift. It is composed entirely from Bootstrap 5 utilities (primarily bg-secondary (or bg-body-secondary in 5.3), rounded, d-flex, gap, and w-* classes) combined with a CSS pulse animation defined in an inline style block. Use it whenever content loads asynchronously: API calls, lazy-loaded images, or paginated feeds. Bootstrap's own name for the pattern is placeholders (.placeholder, .placeholder-glow and .placeholder-wave), available since 5.1.
Bootstrap class
.placeholder, .placeholder-glow, .placeholder-waveCommon use cases
- A social media dashboard shows skeleton cards for each post whilst the activity feed loads from a REST API, preventing the page from appearing broken.
- An e-commerce product listing page renders skeleton grid items in place of product images and prices during the initial data fetch, keeping the layout stable.
- A user profile page displays a circular skeleton avatar and three lines of skeleton text whilst the user record is retrieved from an authentication service.
- A data table renders skeleton rows with varying column widths whilst a paginated report dataset is fetched, giving users an accurate preview of the incoming table structure.
Variants and classes
| Variant | Description and classes |
|---|---|
| Native placeholders | Bootstrap's built-in loading shapes, sized with col-* and placeholder-lg, -sm or -xs.placeholder col-6 + placeholder-glow / placeholder-wave |
| Text Block Skeleton | Stacked horizontal bars of varying widths simulate a paragraph of text. Uses Bootstrap w-* utilities (w-100, w-75, w-50) on thin div elements styled with a fixed height.bg-body-secondary rounded mb-2 |
| Card Skeleton | Combines a full-width image placeholder with a text block skeleton inside a Bootstrap card shell, composing .card, .card-body, and flex utilities.card border-0 shadow-sm |
| Avatar and Text Row Skeleton | Places a circular placeholder beside stacked text bars using d-flex, align-items-center, and gap-3: common in comment lists, user tables, and notification panels.d-flex align-items-center gap-3 |
| Table Row Skeleton | Renders skeleton cells inside a standard Bootstrap table structure, using col-span-aware widths and the pulse animation on each td's inner div to match real table content.table table-borderless |
Code example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 5 Skeleton Loader</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<style>
.skeleton-pulse {
animation: skeleton-pulse-anim 1.5s ease-in-out infinite;
}
@keyframes skeleton-pulse-anim {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
.skeleton-line {
height: 0.85rem;
border-radius: 0.25rem;
}
.skeleton-img {
height: 180px;
border-radius: 0.375rem 0.375rem 0 0;
}
.skeleton-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
flex-shrink: 0;
}
</style>
</head>
<body class="bg-light p-4">
<div class="container">
<h1 class="h4 mb-4">Skeleton Loaders</h1>
<!-- Card skeleton -->
<div class="row g-4 mb-5">
<div class="col-12 col-sm-6 col-md-4" aria-busy="true" aria-label="Loading content">
<div class="card border-0 shadow-sm overflow-hidden">
<div class="skeleton-img skeleton-pulse bg-body-secondary w-100"></div>
<div class="card-body">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-100 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4" aria-busy="true" aria-label="Loading content">
<div class="card border-0 shadow-sm overflow-hidden">
<div class="skeleton-img skeleton-pulse bg-body-secondary w-100"></div>
<div class="card-body">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-100 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-md-4" aria-busy="true" aria-label="Loading content">
<div class="card border-0 shadow-sm overflow-hidden">
<div class="skeleton-img skeleton-pulse bg-body-secondary w-100"></div>
<div class="card-body">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-100 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div>
</div>
</div>
</div>
</div>
<!-- Avatar + text row skeleton -->
<h2 class="h6 text-muted mb-3">User List Skeleton</h2>
<div class="d-flex flex-column gap-3 mb-5">
<div class="d-flex align-items-center gap-3" aria-busy="true" aria-label="Loading user">
<div class="skeleton-avatar skeleton-pulse bg-body-secondary"></div>
<div class="flex-grow-1">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div>
</div>
</div>
<div class="d-flex align-items-center gap-3" aria-busy="true" aria-label="Loading user">
<div class="skeleton-avatar skeleton-pulse bg-body-secondary"></div>
<div class="flex-grow-1">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div>
</div>
</div>
<div class="d-flex align-items-center gap-3" aria-busy="true" aria-label="Loading user">
<div class="skeleton-avatar skeleton-pulse bg-body-secondary"></div>
<div class="flex-grow-1">
<div class="skeleton-line skeleton-pulse bg-body-secondary w-50 mb-2"></div>
<div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div>
</div>
</div>
</div>
<!-- Table skeleton -->
<h2 class="h6 text-muted mb-3">Table Skeleton</h2>
<table class="table table-borderless" aria-busy="true" aria-label="Loading table data">
<thead>
<tr>
<th><div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div></th>
<th><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></th>
<th><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></th>
<th><div class="skeleton-line skeleton-pulse bg-body-secondary w-25"></div></th>
</tr>
</thead>
<tbody>
<tr>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-100"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></td>
</tr>
<tr>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-100"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></td>
</tr>
<tr>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-100"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-75"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-50"></div></td>
<td><div class="skeleton-line skeleton-pulse bg-body-secondary w-25"></div></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>Live examples
Native Bootstrap placeholders
Bootstrap's built-in .placeholder classes: widths from col-* classes and a pulse from placeholder-glow, no custom CSS.
Text Paragraph Skeleton
A minimal text-only skeleton with four lines of varying width, suitable for article bodies or description panels loading asynchronously.
Avatar and Name Row Skeleton
Circular avatar placeholder alongside two text lines: ideal for comment threads, notification lists, or team member directories awaiting API data.
Product Card Skeleton
Full card skeleton with an image block and three text lines inside a Bootstrap card, matching the layout of a typical e-commerce product tile.
Canvas template variants
Canvas Builder builds pages on the Canvas HTML template, which adds its own styled versions of Bootstrap 5 components. Variants you can ask for in a prompt:
- Canvas Builder: generate a 3-column skeleton card grid from a single prompt
- Canvas Builder: avatar-and-text skeleton list for a notifications panel
- Canvas Builder: full-page skeleton layout with sidebar and main content area
- Canvas Builder: skeleton table with header and five data rows
- Canvas Builder: dashboard skeleton with stat tiles and a chart placeholder
Best practices
Use aria-busy and aria-label on the container
Always add aria-busy="true" to the outermost skeleton wrapper so screen readers announce that content is loading. Pair it with a descriptive aria-label such as "Loading product list" so assistive technology users understand what to expect. Remove the skeleton from the DOM entirely once real content is available. Do not just hide it with d-none, as hidden aria-busy elements can still confuse some screen readers.
Match skeleton dimensions to real content
The skeleton loses its value if its proportions differ significantly from the real content it replaces, because users will see a jarring layout shift on load. Measure the actual rendered heights and widths of your headings, images, and text lines, then hard-code those values in inline styles or a small custom CSS block. Bootstrap's w-* utilities cover 25, 50, 75, and 100 per cent widths, which is usually sufficient for text lines but image blocks typically need an explicit height.
Prefer opacity animation over background-color shimmer for broad browser support
A shimmer effect (moving gradient left to right) requires a CSS background-size and animation-direction: alternate approach that can conflict with Bootstrap's bg-* utilities when dark mode is active. A simple opacity pulse defined as a single @keyframes block avoids this entirely, works correctly in both Bootstrap 5.3 light and dark colour modes (bg-body-secondary adapts automatically), and has no dependency on JavaScript. If you do want a shimmer, override the background with a standalone CSS custom property rather than touching Bootstrap's colour utilities.
Disable interactive elements inside skeletons with the disabled class
Any buttons or links inside a skeleton block should carry both the disabled and placeholder classes. This prevents accidental clicks and visually signals the non-interactive state without extra JavaScript.