Spinner
FreeAn animated circular indicator for indeterminate loading states. The spinner rotates continuously to communicate that content or a process is loading. Available in multiple sizes for use as a page-level loader, section placeholder, or inline indicator. Always pair with an accessible label so assistive technology can announce the loading state.
6 variants
Variants
| Variant | Description | When to use |
|---|---|---|
| Small | Compact 16px spinner for inline use within buttons or table cells. | Use inside buttons during async submission or within compact UI elements. |
| Medium | Standard 24px spinner for section-level loading states. | Use as a placeholder within a card, panel, or content section that is loading. |
| Large | Prominent 48px spinner for page-level loading states. | Use as the primary loading indicator when an entire view is loading. |
Usage Guidelines
Always provide an accessible label via aria-label="Loading" on the spinner.
Use a spinner when you know the progress percentage — use Progress Bar instead.
Alternatively, set aria-busy="true" on the container that is loading.
Display a spinner indefinitely without a timeout — show an error state after a reasonable wait.
Use the appropriate size — small for inline, medium for sections, large for pages.
Stack multiple spinners in the same view.
Remove the spinner and update aria-busy="false" when loading completes.
Code
<div class="lex-spinner" role="status" aria-label="Loading">
<svg class="lex-spinner__svg" viewBox="0 0 24 24"
fill="none" xmlns="http://www.w3.org/2000/svg">
<circle class="lex-spinner__track" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="3" opacity="0.2" />
<circle class="lex-spinner__arc" cx="12" cy="12" r="10"
stroke="currentColor" stroke-width="3"
stroke-linecap="round" stroke-dasharray="32 64" />
</svg>
</div>
<!-- Inline spinner within a button -->
<button class="lex-button lex-button--primary" disabled aria-busy="true">
<span class="lex-spinner lex-spinner--sm" role="status" aria-label="Saving">
<svg class="lex-spinner__svg" viewBox="0 0 24 24"><!-- ... --></svg>
</span>
Saving…
</button>.lex-spinner {
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--lex-spinner-color, var(--lex-text-brand));
}
.lex-spinner--sm { width: 16px; height: 16px; }
.lex-spinner--md { width: 24px; height: 24px; }
.lex-spinner--lg { width: 48px; height: 48px; }
.lex-spinner__svg {
width: 100%;
height: 100%;
animation: lex-spin 750ms linear infinite;
}
@keyframes lex-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.lex-spinner__track {
opacity: 0.2;
}
.lex-spinner__arc {
stroke-dasharray: 32 64;
stroke-linecap: round;
}
@media (prefers-reduced-motion: reduce) {
.lex-spinner__svg {
animation-duration: 1.5s;
}
}// Using Lexicon CSS classes with React
export function PageLoader() {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: '4rem' }}>
<Spinner size="lg" label="Loading dashboard" />
</div>
);
}
export function SubmitButton({ isLoading }: { isLoading: boolean }) {
return (
<Button variant="primary" disabled={isLoading} aria-busy={isLoading}>
{isLoading && <Spinner size="sm" />}
{isLoading ? 'Saving…' : 'Save changes'}
</Button>
);
}Design Tokens
| Token | Value (dark) | Value (light) | CSS property |
|---|---|---|---|
| --lex-spinner-size | — | — | size |
| --lex-spinner-color | — | — | color |
| --lex-spinner-track-color | — | — | color |
| --lex-spinner-speed | — | — | speed |
Accessibility
- Always include role="status" and aria-label="Loading" (or a more specific label) on the spinner.
- Alternatively, set aria-busy="true" on the container element that is loading, and remove it when complete.
- Respect prefers-reduced-motion by slowing or pausing the animation.
- When used inside a button, the button should also be disabled and have aria-busy="true".
- Remove the spinner from the DOM (or set aria-hidden="true") when loading completes.