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.
Primary Class
bg-body-secondary rounded 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 & Classes
| Variant | Description |
|---|---|
| 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. |
| 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. |
| 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. |
| 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. |
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
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 Framework Variants
The Canvas template extends Bootstrap 5 with 1,658+ component variants. Generate any of these using Canvas Builder:
- ✓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.