AdminKitDocs
Components

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.

Pro

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

ClassEffect
spinner-borderA turning ring
spinner-growA dot that grows and fades
spinner-border-sm, spinner-grow-smA 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 a visually-hidden text 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.

Last updated on

On this page