Skip to main content
Pricing

Spinner

Free

An 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

Size SM
Size MD
Size LG

Variants

VariantDescriptionWhen to use
SmallCompact 16px spinner for inline use within buttons or table cells.Use inside buttons during async submission or within compact UI elements.
MediumStandard 24px spinner for section-level loading states.Use as a placeholder within a card, panel, or content section that is loading.
LargeProminent 48px spinner for page-level loading states.Use as the primary loading indicator when an entire view is loading.

Usage Guidelines

Do

Always provide an accessible label via aria-label="Loading" on the spinner.

Don't

Use a spinner when you know the progress percentage — use Progress Bar instead.

Do

Alternatively, set aria-busy="true" on the container that is loading.

Don't

Display a spinner indefinitely without a timeout — show an error state after a reasonable wait.

Do

Use the appropriate size — small for inline, medium for sections, large for pages.

Don't

Stack multiple spinners in the same view.

Do

Remove the spinner and update aria-busy="false" when loading completes.

Don't

Code

HTML
<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>
CSS Custom Properties
.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;
  }
}
React
// 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

TokenValue (dark)Value (light)CSS property
--lex-spinner-sizesize
--lex-spinner-colorcolor
--lex-spinner-track-colorcolor
--lex-spinner-speedspeed

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.

Keyboard Interactions

N/ASpinners are not interactive and do not receive focus.