Spinner
An indeterminate loading indicator — a spinning lucide icon that inherits currentColor and announces itself as a status.
- Status
- Since
0.1.0- Accessibility pattern
- role=status named Loading
Last updated
Install
Configure registry credentials first using the Quickstart. Before copying this item or any transitive registry dependency, verify the signed manifest and save the exact bytes for Spinner before installation. Retain the digest printed by this command.
pnpm exec vegastack-design verify spinnerInstall with the pinned shadcn CLI. Bare shadcn does not verify VegaStack signature or integrity.
pnpm dlx shadcn@4.21.0 add @vegastack/spinnerRun the exact offline --post-write command printed by the preflight, using its saved item path and independently retained --expected-integrity digest. Use the saved preflight for every transitive registry dependency too; stop on any mismatch before using the components.
Usage
import { Spinner } from "@/components/ui/spinner";
<Spinner />;Examples
Customization
The spinner is a single lucide icon, so swapping the mark is a one-line edit to the component: the
role, the accessible name and the size-4 animate-spin recipe stay where they are. The first
mark below is the default, lucide Loader; the second is LoaderCircleIcon in its place.
Size
Use the size-* utility class to change the size of the spinner. It has no size prop — passing
size, width or height to a lucide component is not how this system sizes icons.
Button
Add a spinner to a button to indicate a loading state. Place the spinner before the label with
data-icon="inline-start" for a start position, or after the label with data-icon="inline-end"
for an end position. For a pending submit that must keep its width, use the Button's own loading
prop instead.
Badge
The same data-icon attributes space a spinner inside a badge.
Input Group
A spinner sits in an InputGroup's trailing addon
(InputGroupAddon align="inline-end") to show a field resolving in place, and in a row beside a
Textarea for the multi-line case.
Validating...
Empty
An Empty block is the right place for a wait long enough to need an explanation and a way out.
RTL
The spinner is direction-agnostic; the row around it mirrors with the document, and data-icon
spacing is written in logical properties.
API Reference
Spinner adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Accessibility
- The icon renders
role="status"witharia-label="Loading", so the wait is announced once without a live region of your own. - When the spinner sits inside a control that already says it is busy — a Button with
loading, an Item whose title reads "Processing payment…" — passaria-label={undefined}so the state is not announced twice. - The colour is
currentColor: the spinner inherits the ink of whatever hosts it, so it never needs a contrast decision of its own. - The rotation is
animate-spin, one of the two sanctioned loader animations, and the global reduced-motion reset inbase.csscollapses it.
| Contract | States tested |
|---|---|
| Behaviour | default, empty, indeterminate, loading |
| Accessibility | labeled, status-announcement |
| Visual | default, loading, empty |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/spinner.patch. Every hunk:
- DOC-2 —
cnis imported from@vegastack/design. - ICO-8 — the mark is
LoaderIcon(lucideloader) in place of upstream'sLoader2Icon, which is an alias ofLoaderCircle. One loader shape across the system: this spinner and the Toast and Sonner loading icons are the same object spinning. Thedata-slot, therole="status", thearia-labeland thesize-4 animate-spinrecipe are upstream's.