# 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](/pricing/#react).

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](/react/).

## Example

```html
<div class="spinner-border text-primary me-3" role="status">
  <span class="visually-hidden">Loading...</span>
</div>
<div class="spinner-grow text-primary" role="status">
  <span class="visually-hidden">Loading...</span>
</div>
```
```tsx code
<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:

```html
<div class="spinner-border text-primary me-2" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="spinner-border text-success me-2" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="spinner-border text-danger me-2" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="spinner-border spinner-border-sm text-primary me-2" role="status"><span class="visually-hidden">Loading...</span></div>
<div class="spinner-border text-primary" style="width: 3rem; height: 3rem;" role="status"><span class="visually-hidden">Loading...</span></div>
```
```tsx code
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:

```html
<div class="card" style="max-width: 24rem;">
  <div class="card-header">
    <h2 class="card-title mb-0">Latest orders</h2>
  </div>
  <div class="card-body d-flex justify-content-center py-5">
    <div class="spinner-border text-primary" role="status">
      <span class="visually-hidden">Loading orders...</span>
    </div>
  </div>
</div>
```
```tsx code
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](/react/docs/components/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](/react/docs/components/buttons/#loading-state) on the Buttons page:

```html
<button type="button" class="btn btn-primary" disabled>
  <span class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>
  Saving...
</button>
```
```tsx code
<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.

## Related

- [Next.js Bootstrap spinners](/nextjs/docs/components/spinners/): the same markup in AdminKit for Next.js
- [Bootstrap 5 spinners](/bootstrap/docs/components/spinners/): the spinners of the HTML version
- [Placeholders](/react/docs/components/placeholders/): loading shapes for cards and text
- [General UI demo](https://react-demo.adminkit.io/ui-general)

---

Canonical HTML page: https://adminkit.io/react/docs/components/spinners/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
