React Bootstrap Spinners
React Bootstrap spinner markup in AdminKit PRO: border and grow spinners, colors and sizes, and a spinner shown from React state while data loads.
This component is only available in AdminKit PRO. Learn more.
Show a spinner when something is loading and you cannot tell how long it will take: a list waiting for your API, or a form that is saving.
There is no spinner component to import. You write Bootstrap's .spinner-border or .spinner-grow markup in JSX, and your React state decides when to render it. Spinners are part of the PRO version of AdminKit for React.
Example
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div className="spinner-grow text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>Classes
| Class | Effect |
|---|---|
spinner-border | A turning ring |
spinner-grow | A dot that grows and fades |
spinner-border-sm, spinner-grow-sm | A smaller spinner, for buttons and inline text |
text-<color> | The color: spinners use currentColor |
For another size, set it with a style object: style={{ width: "3rem", height: "3rem" }}.
With reduced motion turned on in the operating system, AdminKit PRO fades the spinners in and out instead of turning or scaling them. That rule is in src/scss/3-components/_spinner.scss. All other spinner styles are Bootstrap's.
Colors and sizes
Pick a color with a text- class and a size with spinner-border-sm or a style object. The general UI demo renders its rows of spinners from a list of colors:
import { Fragment } from "react";
{["primary", "success", "danger"].map((color) => (
<Fragment key={color}>
<div className={`spinner-border text-${color} me-2`} role="status">
<span className="visually-hidden">Loading...</span>
</div>{" "}
</Fragment>
))}
<div className="spinner-border spinner-border-sm text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<div className="spinner-border text-primary" style={{ width: "3rem", height: "3rem" }} role="status">
<span className="visually-hidden">Loading...</span>
</div>While data loads
The most common case is a card that waits for data from your API. Render the spinner while the request runs, then the content. Center the spinner in the space the content will fill:
import { useEffect, useState } from "react";
type Order = { id: string; customer: string };
export function LatestOrders() {
const [orders, setOrders] = useState<Order[] | null>(null);
useEffect(() => {
fetch("/api/orders?limit=5")
.then((response) => response.json())
.then(setOrders);
}, []);
return (
<div className="card">
<div className="card-header">
<h2 className="card-title mb-0">Latest orders</h2>
</div>
{orders === null ? (
<div className="card-body d-flex justify-content-center py-5">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading orders...</span>
</div>
</div>
) : (
<ul className="list-group list-group-flush">
{orders.map((order) => (
<li key={order.id} className="list-group-item">
{order.customer}
</li>
))}
</ul>
)}
</div>
);
}For content with a known shape, such as a card or a table, placeholders keep the layout from jumping when the data arrives.
You can also show a spinner for a whole page. src/app.tsx loads every page with React.lazy, and each layout wraps its pages in a <Suspense> without a fallback. Give the dashboard layout's Suspense a fallback with a spinner, and it shows while a page's code downloads.
In a button
A spinner in a button tells the user that their click is being handled. There it is decoration next to the text, so hide it with aria-hidden="true" and keep a visible text. The full example is the loading button on the Buttons page:
<button type="button" className="btn btn-primary" disabled={saving}>
{saving && <span className="spinner-border spinner-border-sm me-2" aria-hidden="true" />}
{saving ? "Saving..." : "Save"}
</button>Accessibility
- A spinner on its own has
role="status"and avisually-hiddentext that says what is loading. - A spinner next to a visible text, as in a button, gets
aria-hidden="true"instead, so the text is not read twice. - Disable a button while its request runs, so it cannot be sent twice.
Related
- Next.js Bootstrap spinners: the same markup in AdminKit for Next.js
- Bootstrap 5 spinners: the spinners of the HTML version
- Placeholders: loading shapes for cards and text
- General UI demo
Last updated on
ProgressPro
A React Bootstrap progress bar in AdminKit PRO: the width and aria-valuenow from one value, labels, sizes, colors, stripes and stacked bars.
Tables
React Bootstrap table markup in AdminKit: rows rendered from an array with map and key, striped, hover and bordered tables, row colors and tables in cards.