Experience System
Components

Spinner

Shows an indeterminate loading state with a token-based gradient ring and size variants.

Spinner renders an indeterminate loading indicator. It uses a gradient foreground ring over a neutral background ring, with thickness scaled by design tokens so density adjustments remain visually balanced.

Installation

The component is exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to render a Spinner:

Spinner

Spinner is a single-export component built from a styled span element and does not require additional parts.

Usage

import { Button, Spinner } from '@by/experience-system';

Spinner is a client component ('use client' in the package). Use it inside a Client Component or a dynamic import when using the Next.js App Router.

A common pattern is an outline Button with a leading Spinner (use data-icon="inline-start" on the spinner so spacing matches icon-leading buttons). Pick a Spinner size explicitly—it is not sized like inline SVG icons inside Button.

<Button type="button" variant="outline" size="md" disabled aria-busy="true">
  <Spinner data-icon="inline-start" size="sm" aria-hidden />
  Saving…
</Button>

You can also render Spinner on its own when no button chrome is needed:

import { Spinner } from '@by/experience-system';

<Spinner size="md" />

Examples

Overview

Show progress on an action by placing Spinner inside an outline Button where you would normally put a leading icon. Disable the control while work is in flight, set aria-busy on the Button, and use aria-hidden on Spinner so assistive technology relies on the button label instead of duplicating a generic “Loading” announcement.

Sizes

Use size to scale the spinner: sm, md (default), lg, and xl.

SM
MD
LG
XL

API Reference

Spinner is a Experience System component (not a Radix primitive). It renders a styled span and forwards native HTML span attributes in addition to the Experience System variant props.

Spinner

Spinner applies spinnerVariants({ size }), sets role="status", defaults aria-label to Loading, and adds data-slot="spinner".

PropTypeDefault
sizesm | md | lg | xlmd
classNamestringundefined

Native React.HTMLAttributes<HTMLSpanElement> props are also supported (for example aria-label, aria-labelledby, id, and style).

Data attributeValues
data-slotspinner

Accessibility

Spinner announces loading state with role="status" and defaults to aria-label="Loading". Override aria-label (or provide aria-labelledby) when a more specific loading message is required by the UI context.

Source in the repo: packages/experience-system/src/components/Spinner/Spinner.tsx and variants.ts. Agent-oriented contracts: packages/experience-system/src/components/Spinner/Spinner.instructions.md.