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-labeldescribing 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-motionto 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: