Feedback components tell users what the application is doing. Toasts are lightweight notifications that appear briefly in a corner, spinners show that something is loading, and placeholders draw skeleton screens while content arrives. This lesson covers all three, including stacking and positioning toasts, sizing and coloring spinners, and animating placeholders.
A toast is hidden until its JavaScript instance calls show(). Toasts require the JavaScript plugin and are placed inside a .toast-container so multiple toasts stack:
<div class="toast-container position-fixed top-0 end-0 p-3">
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" id="errorToast">
<div class="toast-header text-bg-danger">
<strong class="me-auto">Upload failed</strong>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">The file exceeds the 5 MB limit.</div>
</div>
</div>Position the container with the position utilities (top-0 end-0, bottom-0 start-50 translate-middle-x, and so on). Use role="alert" with aria-live="assertive" for errors and role="status" with aria-live="polite" for informational messages.
import { Toast } from "bootstrap";
const toast = Toast.getOrCreateInstance(document.getElementById("errorToast"), {
autohide: true,
delay: 5000,
animation: true
});
toast.show();| Option | Default | Meaning |
|---|---|---|
| animation | true | Fade in and out |
| autohide | true | Hide automatically |
| delay | 5000 | Milliseconds before auto-hiding |
Set autohide: false (or data-bs-autohide="false") for messages that need a user action. Events are show, shown, hide and hidden with the .bs.toast suffix.
Most apps generate toasts on the fly rather than keeping them in the HTML:
function notify(message, variant = "primary") {
const container = document.querySelector(".toast-container");
const el = document.createElement("div");
el.className = `toast align-items-center text-bg-${variant} border-0`;
el.setAttribute("role", "status");
el.setAttribute("aria-live", "polite");
el.innerHTML = `
<div class="d-flex">
<div class="toast-body">${message}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>`;
container.appendChild(el);
el.addEventListener("hidden.bs.toast", () => el.remove());
Toast.getOrCreateInstance(el).show();
}
notify("Settings saved", "success");This is the compact, header-less toast style with text-bg-* color. Removing the element after hidden.bs.toast keeps the DOM clean.
Spinners are CSS-only. Two styles exist:
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-secondary" role="status">
<span class="visually-hidden">Loading...</span>
</div>text-{color} utilities.spinner-border-sm / spinner-grow-sm, or set style="width: 3rem; height: 3rem" for larger sizes.Inside a button, place a small spinner before the label and hide the spinner until needed, as in the sample at the top. If the button text is removed while loading, keep a .visually-hidden label. Center a standalone spinner with d-flex justify-content-center on the parent or d-block mx-auto on the spinner itself.
Placeholders render grey blocks the shape of upcoming content. They are also CSS-only:
<div class="card" aria-hidden="true" style="max-width: 320px">
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-6"></span>
</h5>
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
</p>
<a class="btn btn-primary disabled placeholder col-6" aria-disabled="true"></a>
</div>
</div>.placeholder draws the block; give it a width with col-* or w-*..placeholder-glow on the parent pulses opacity; .placeholder-wave sweeps a highlight across.placeholder-xs, -sm and -lg change the height; bg-* changes the color.aria-hidden="true" so screen readers skip it, and replace it with real content once loaded.A typical flow: render the skeleton, fetch data, then swap innerHTML. Because placeholders are just spans, the swap needs no plugin cleanup.
| Situation | Component | |---|---| | Confirming a completed action | Toast | | Short wait on a button or small area | Spinner | | Content that has a known shape (cards, lists) | Placeholder | | Blocking error that needs acknowledgement | Modal or alert, not a toast |
show() before the JavaScript is loaded. The toast stays hidden; check for bootstrap.Toast in the console..toast-container. They overlap instead of stacking.aria-hidden="true".What does the `delay` option of a toast control?
.toast-container and need Toast.getOrCreateInstance(el).show(); autohide and delay control dismissal.hidden.bs.toast; use role/aria-live appropriate to the message..spinner-border and .spinner-grow are CSS-only; color with text-*, size with -sm or inline width/height, and include hidden label text..placeholder spans with placeholder-glow or placeholder-wave build skeleton screens; mark them aria-hidden.Next lesson: Scrollspy - highlight navigation links automatically as the user scrolls through page sections.