Spinner
An animated indeterminate loading indicator. For a content-shaped placeholder use Skeleton; for a determinate task use Progress.
Usage
import { Spinner } from "@opengovsg/oui"<Spinner />import { Spinner } from "@opengovsg/oui"export const Example = () => { return <Spinner />}Alternatively, install the component as local source via the shadcn CLI:
npx shadcn@latest add https://oui.open.gov.sg/r/spinner.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/spinner.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/spinner.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/spinner.jsonSpinner is an indeterminate loading indicator: two animated rings that signal an ongoing operation of unknown duration. It renders with role="progressbar" and a default aria-label of "Loading", so it announces itself to assistive technology out of the box.
When the spinner has no visible label, leave the default aria-label in place or supply a more specific one describing what is loading.
When to use
Use Spinner for short, indeterminate waits where the duration is unknown. Use Skeleton when you can show the shape of the content that is loading. Use Progress when the task has a measurable, determinate completion percentage. For an inline loading state on a button, prefer the Button isPending prop documented in Button.
Examples
Sizes
Use the size prop to change the size of the spinner.
import { Spinner } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex flex-row items-end gap-6"> <Spinner size="xs" /> <Spinner size="sm" /> <Spinner size="md" /> <Spinner size="lg" /> </div> )}Colors
Use the color prop to switch between the current text color and white. Use white when placing the spinner on a dark or colored background.
import { Spinner } from "@opengovsg/oui"export const Example = () => { return ( <div className="flex flex-row items-center gap-6"> <Spinner color="current" /> <div className="flex items-center rounded-md bg-gray-800 p-4"> <Spinner color="white" /> </div> </div> )}Accessibility
- Spinner renders with
role="progressbar"and a defaultaria-labelof"Loading"; assistive technology announces it as an indeterminate progress indicator. - Pass a more specific
aria-label(e.g."Loading results") when the default is not descriptive enough for the context. - Color is not used to convey state; the spinner only signals that something is loading.
Slots
Slots are named regions of the component you can target with custom Tailwind classes via the classNames prop. Each slot below corresponds to a key on the classNames object.
base: The outer<div>container that carries theprogressbarrole.wrapper: The<div>that sizes the rings and positions them relative to each other.circle1: The first (solid-border) animated ring<i>.circle2: The second (dotted-border) animated ring<i>.
Custom Styles
Pass classes to individual slots to recolor the rings. The example below tints each ring with a different brand color.
import { Spinner } from "@opengovsg/oui"export const Example = () => { return ( <Spinner size="lg" classNames={{ circle1: "border-b-brand-primary-500", circle2: "border-b-brand-secondary-400", }} /> )}Props
Spinner
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "md" | The size of the spinner |
color | "current" | "white" | "current" | The ring color; current follows text color, white for dark backgrounds |
aria-label | string | "Loading" | Accessible label announced by assistive technology |
classNames | SlotsToClasses<SpinnerSlots> | - | Custom Tailwind classes for component slots |
className | string | - | Additional class applied to the base element |
Inherits all remaining props from the underlying <div> element (e.g. id, style, data-* attributes).