On this page
Spinner
Set
aria-busy="true"
on the host that is loading. CSS paints the disc. Remove the
attribute when the work is done. There is no extra
.spinner-border
child and no spinner plugin. A known percent is a
<progress>.
| What runs it | JavaScript | |
|---|---|---|
| Bootstrap 5.3 | No Spinner plugin. Markup is .spinner-border plus visually-hidden text. | Not in bootstrap.min.js |
| This repo | `aria-busy="true"` on the host. CSS paints the disc. | 0 KB |
| Avoid |
|
|---|
How it works
-
The attribute is the state. CSS matches
[aria-busy="true"]. Your app sets it and clears it. This package ships no script for that. - A block host hides its children visually and centers a disc. Keep the children’s box so the layout does not collapse.
-
A
<button>or.btnkeeps its name. The disc is a small::beforeat.spinner-border-smsize. Adddisabledso a second click does not submit. -
An inline
<span>keeps its text and shows a smaller disc ahead of it. -
prefers-reduced-motionslows the turn. It does not freeze the disc. -
.spinner-borderremains in the stylesheet if you need a disc that is not a busy host. Do not put one inside a host that already hasaria-busy.
Toggle busy on each example is a docs demo, not required markup.
Buttons
The idle control and the busy one share a name. disabled blocks a second press.
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-primary">Publish</button>
<button type="button" class="btn btn-primary" aria-busy="true" disabled>Publish</button>
<button type="button" class="btn btn-outline-secondary" aria-busy="true" disabled>Export CSS</button>
<button type="button" class="btn btn-sm btn-primary" aria-busy="true" disabled>Publish</button>
</div> Card
role=status names the host while the body is visually hidden. The box stays.
Field sizing
The control grows to its contents.
Field sizing
The control grows to its contents.
<div class="row g-3">
<div class="col-md-6">
<div class="card h-100" role="status" aria-busy="true" aria-label="Loading page notes">
<div class="card-body">
<h3 class="card-title h5">Field sizing</h3>
<p class="card-text mb-0">The control grows to its contents.</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-body">
<h3 class="card-title h5">Field sizing</h3>
<p class="card-text mb-0">The control grows to its contents.</p>
</div>
</div>
</div>
</div> Inline
The status text stays. A small disc sits before it.
- Compiling Sass…
- Writing changelog…
- Inventory updated
<ul class="list-unstyled mb-0">
<li class="mb-2"><span aria-busy="true">Compiling Sass…</span></li>
<li class="mb-2"><span aria-busy="true">Writing changelog…</span></li>
<li>Inventory updated</li>
</ul> Table
Put aria-busy on the table. Rows keep their height under the disc.
| Page | Baseline |
|---|---|
| Tooltip | Newly |
| Carousel | Experimental |
<table class="table mb-0" aria-busy="true" aria-label="Loading catalog">
<thead>
<tr>
<th scope="col">Page</th>
<th scope="col">Baseline</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tooltip</td>
<td>Newly</td>
</tr>
<tr>
<td>Carousel</td>
<td>Experimental</td>
</tr>
</tbody>
</table> Live region
Assistive technology waits until you clear aria-busy before announcing the update.
Indexing the catalog…
<div class="alert alert-info mb-0" role="status" aria-live="polite" aria-busy="true">
<p class="mb-0">Indexing the catalog…</p>
</div> Standalone
An empty host still needs a name. min-block-size comes from CSS when there are no children.
<div role="status" aria-busy="true" aria-label="Loading catalog"></div> Accessibility
The attribute is the state. The disc is decorative (::before / ::after). Do not add role="status" on a spinner element.
A nameless host needs a role that can take a name (role="status") and aria-label (or a more specific name) while it is busy. A generic <div> with only aria-label is ignored.
A button keeps its accessible name. disabled blocks a second submit.
On a live region, aria-busy="true" tells assistive technology to wait until you set it to false (or remove it) before announcing the update.
prefers-reduced-motion slows the turn. It does not freeze the disc, so the busy state stays visible.
Browser support
| No script | Polyfill | No fallback |
|---|---|---|
aria-busy is a global ARIA state. The disc is CSS on [aria-busy="true"]. Removing the attribute hides it. | None | Without this CSS the attribute still tells assistive technology. There is no disc. .spinner-border stays in the sheet as a standalone glyph. |