CSS-Only

Spinner

Pure CSS loading spinner with size and color variants for async feedback. Zero JavaScript required.

Example

<div class="omni-spinner" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="primary" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="success" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="danger" role="status" aria-label="Loading"></div>

Variants

Semantic Variants

Use data-variant to convey semantic meaning through color:

<div class="omni-spinner" data-variant="primary" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="secondary" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="success" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="warning" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="danger" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="info" role="status" aria-label="Loading"></div>

Sizes

Use data-size to adjust spinner dimensions. Inherits size from variant system for sm/md/lg, with extra xs/xl options:

<div class="omni-spinner" data-variant="primary" data-size="xs" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="primary" data-size="sm" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="primary" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="primary" data-size="lg" role="status" aria-label="Loading"></div>
<div class="omni-spinner" data-variant="primary" data-size="xl" role="status" aria-label="Loading"></div>

In Context

Spinners work inline with text and inside buttons:

Inline with Text

Loading content...

Processing your request...

<p style="display: flex; align-items: center; gap: 0.5rem;">
  <span class="omni-spinner" data-size="sm" role="status" aria-label="Loading"></span>
  Loading content...
</p>

Inside Buttons

<button class="omni-button" data-variant="primary">
  <span class="omni-spinner" data-size="sm" role="status" aria-label="Saving"></span>
  Saving...
</button>

Centered Loading State

Loading content...

<div style="display: flex; flex-direction: column; align-items: center; gap: 1rem; padding: 2rem;">
  <div class="omni-spinner" data-variant="primary" data-size="lg" role="status" aria-label="Loading content"></div>
  <p style="margin: 0; color: var(--color-text-muted);">Loading content...</p>
</div>

API Reference

Attribute Values Default Description
data-variant primary, secondary, success, warning, danger, info Semantic color variant (uses currentColor by default)
data-size xs, sm, md, lg, xl md Spinner dimensions (xs: 0.75rem, sm: inherits variant, md: 1.25rem, lg: inherits variant, xl: 3rem)
role status ARIA role for screen readers (required)
aria-label string Accessible label describing the loading state (required)

Accessibility

  • Always include role="status" to announce loading state to screen readers
  • Always provide aria-label describing what is loading (e.g., "Loading content", "Saving data")
  • Avoid hiding spinners with aria-hidden="true" as this removes critical feedback
  • Provide visible text alongside the spinner when possible for clarity
  • Respects prefers-reduced-motion to disable animation for users sensitive to motion
  • Pure CSS animation runs efficiently without JavaScript overhead
  • Uses semantic color variants to convey state meaning (success, danger, etc.)

Components Used

Other OmniUI components demonstrated on this page: