Spinner
The Spinner component provides animated loading indicators with four distinct animation styles. It dynamically loads SVG animations, offers size and speed control, and includes fallback states for robust loading experiences.
Spinner Types
Four distinct animation styles for different contexts
<Spinner type="three-dots" /><Spinner type="12-dots" /><Spinner type="gooey-1" /><Spinner type="gooey-2" />API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | 'three-dots' \| '12-dots' \| 'gooey-1' \| 'gooey-2' | 'three-dots' | Animation style |
size | number | 24 | Size in pixels |
color | string | 'currentColor' | Spinner color |
speed | 'slow' \| 'normal' \| 'fast' | 'normal' | Animation speed |
class | string | '' | Additional CSS classes |
Animation Types
| Type | Description | Best For |
|---|---|---|
three-dots | Simple horizontal dots | Minimal interfaces, inline loading |
12-dots | Circular scaling dots | Primary loading states |
gooey-1 | Organic blob animation | Playful, modern interfaces |
gooey-2 | Fluid morphing animation | Creative, distinctive loading |
Speed Modifiers
| Speed | Duration Multiplier | Use Case |
|---|---|---|
slow | 1.5x | Calm, patient interfaces |
normal | 1x | Standard loading indication |
fast | 0.75x | Quick, responsive feedback |
Usage Examples
Basic Spinner
<Spinner /> Custom Type and Size
<Spinner type="12-dots" size={32} />
<Spinner type="gooey-1" size={48} /> Button with Loading State
<script>
let isLoading = $state(false);
async function handleSubmit() {
isLoading = true;
await saveData();
isLoading = false;
}
</script>
<Button onclick={handleSubmit} disabled={isLoading}>
{#if isLoading}
<Spinner size={16} color="currentColor" />
Saving...
{:else}
Save Changes
{/if}
</Button> Inline Loading Indicator
<p>
Fetching latest data
<Spinner size={14} type="three-dots" class="inline-spinner" />
</p> Full Page Loading
<div class="loading-overlay">
<Spinner type="gooey-1" size={64} />
<p>Loading application...</p>
</div> Input with Search Indicator
<div class="search-input">
<input type="text" placeholder="Searching..." />
{#if isSearching}
<Spinner size={20} class="search-spinner" />
{/if}
</div> Design Patterns
1. Loading States
Different spinner types convey different loading experiences:
- three-dots: Subtle, unobtrusive loading
- 12-dots: Clear, prominent loading indication
- gooey: Engaging, modern loading experience
2. Size Guidelines
Choose sizes based on context:
- 14-16px: Inline text, small buttons
- 20-24px: Form inputs, regular buttons
- 32-40px: Cards, sections
- 48-64px: Full page, modals
3. Color Inheritance
By default, spinners use currentColor, inheriting the text color of their parent. This ensures consistency with your design system:
<!-- Inherits button's text color -->
<Button variant="primary">
<Spinner size={16} />
</Button>
<!-- Explicit color -->
<Spinner color="var(--color-semantic-info)" /> Performance Considerations
Dynamic Loading
The Spinner component loads SVG files on-demand:
- Lazy Loading: SVGs are fetched only when rendered
- Caching: Browser caches loaded SVGs automatically
- Fallback: Shows inline SVG if external file fails
Animation Performance
- Uses CSS animations for smooth 60fps performance
- No JavaScript animation loops
- GPU-accelerated transforms where possible
Bundle Size
- Component: ~4.5KB uncompressed
- SVG files loaded separately (not bundled)
- Tree-shakeable when not used
Accessibility
Screen Reader Support
<!-- Visible loading text -->
<Spinner size={24} />
<span>Loading content...</span>
<!-- Hidden from screen readers -->
<div aria-busy="true" aria-live="polite">
<Spinner size={24} />
<span class="sr-only">Loading...</span>
</div> ARIA Attributes
The component includes:
role="status"for loading indicationaria-label="Loading..."for context- Works with
aria-busyon containers
Focus Management
Spinners don’t receive focus, but consider:
- Disabling interactive elements during loading
- Announcing loading completion to screen readers
- Maintaining focus position after loading
States and Error Handling
Loading States
The component handles three states internally:
- Loading: Shows pulse animation while fetching SVG
- Success: Displays the animated spinner
- Error: Shows fallback inline SVG
Fallback Behavior
<!-- If SVG fails to load, shows built-in fallback -->
<Spinner type="invalid-type" />
<!-- Renders simple animated dots --> Styling
Custom Classes
<Spinner class="my-spinner" />
<style>
:global(.my-spinner) {
opacity: 0.8;
margin: 0 8px;
}
</style> Inline Positioning
<style>
:global(.inline-spinner) {
vertical-align: middle;
margin: 0 4px;
}
</style> Best Practices
Do’s
- ✅ Match spinner size to context (small for buttons, large for pages)
- ✅ Provide loading text for clarity
- ✅ Disable interactions during loading
- ✅ Use consistent spinner types across your app
Don’ts
- ❌ Don’t use multiple spinner types in the same context
- ❌ Don’t make spinners too small (< 14px) or too large (> 64px)
- ❌ Don’t forget to handle loading completion
- ❌ Don’t use fast speed for long operations
Migration Guide
If migrating from static loading indicators:
<!-- Before: CSS-only spinner -->
<div class="css-spinner"></div>
<!-- After: Spinner component -->
<Spinner type="12-dots" size={24} /> For custom SVG spinners:
<!-- Before: Inline SVG -->
<svg class="spinner">...</svg>
<!-- After: Add to /static/svg/spinners/ and use -->
<Spinner type="your-spinner" /> Related Components
- Button: Often contains spinners for loading states
- Card: May show spinners while content loads
- Icon: Static icons, while Spinner provides animated ones
- Skeleton: Alternative loading pattern for content
TypeScript Support
The component is fully typed:
interface Props {
type?: 'three-dots' | '12-dots' | 'gooey-1' | 'gooey-2';
size?: number;
color?: string;
speed?: 'slow' | 'normal' | 'fast';
class?: string;
}