Spinner
The Spinner component displays a loading indicator to show that content is being loaded or processed. It provides visual feedback to users during asynchronous operations, preventing confusion and improving perceived performance.
Import
import { Spinner } from '@flexi-ui/spinner'Usage
A basic spinner with default settings:
Sizes
Spinners come in three sizes to fit different contexts:
Sizes with Labels
Colors
Choose from various color schemes to match your design:
Special Colors
The white and current colors adapt to their context:
Labels
Add descriptive text to provide context:
Label Colors
Customize the label color independently from the spinner:
Integration Examples
In Buttons
Use spinners to indicate loading states in buttons:
In Cards
Center spinners in card components for loading states:
Inline Loading
Display spinners inline with text:
Full Page Loading
Create full-page loading overlays:
In Input Fields
Show loading states next to form inputs:
In Lists
Indicate loading for list items:
Custom Styling
Using className
Add custom classes to the spinner:
Using classNames
Customize individual slots:
Colored Backgrounds
Spinners on different colored backgrounds:
Conditional Loading
Basic Toggle
Toggle between loading and loaded states:
With Error Handling
Handle loading, success, and error states:
Progress Indicator
Combine spinner with progress text:
Real-World Examples
Form Submission
A realistic form submission with loading state:
Data Table Loading
Show loading state in a data table:
Infinite Scroll
Implement infinite scroll with loading indicator:
API Reference
Spinner Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | The size of the spinner |
color | 'current' | 'white' | 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'primary' | The color theme of the spinner |
label | string | - | Optional label text to display below the spinner |
labelColor | 'foreground' | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'foreground' | The color of the label text |
className | string | - | Additional CSS classes for the base element |
classNames | SpinnerSlots | - | Custom classes for individual slots |
SpinnerSlots
The classNames prop accepts an object with the following keys:
| Slot | Description |
|---|---|
base | The main wrapper element that contains the spinner and label |
wrapper | The container for the spinning circles |
circle1 | The first (outer) circle of the spinner animation |
circle2 | The second (inner) circle of the spinner animation |
label | The label text element |
Size Reference
| Size | Spinner Dimensions | Best Use Case |
|---|---|---|
sm | 16x16px | Inline with text, buttons, small UI elements |
md | 32x32px | Default size, cards, forms, moderate spaces |
lg | 48x48px | Full page loading, large containers, prominent feedback |
Color Reference
| Color | Usage |
|---|---|
default | Neutral loading states, general purpose |
primary | Main actions, important loading states |
secondary | Secondary actions, alternative loading states |
success | Successful operations, data syncing |
warning | Warning states, caution needed |
error | Error recovery, retry operations |
white | Dark backgrounds, colored containers |
current | Inherit parent text color, buttons, inline elements |
Accessibility
The Spinner component is built with accessibility in mind:
ARIA Attributes
The component includes proper ARIA attributes:
Default ARIA (role="status")
With aria-label (no visible label)
Screen Reader Announcements
Use with live regions for dynamic updates:
<div aria-live="polite" aria-atomic="true">
{isLoading && <Spinner label="Loading your data..." />}
</div>Focus Management
During loading states, manage focus appropriately:
import { useEffect, useRef } from 'react'
function LoadingComponent() {
const [isLoading, setIsLoading] = useState(true)
const contentRef = useRef(null)
useEffect(() => {
if (!isLoading && contentRef.current) {
// Focus first interactive element after loading
contentRef.current.focus()
}
}, [isLoading])
if (isLoading) {
return <Spinner label="Loading content..." />
}
return (
<div ref={contentRef} tabIndex={-1}>
{/* Your content */}
</div>
)
}Best Practices for Accessibility
- Always provide context: Use either a visible
labeloraria-labelto describe what's loading - Use appropriate live regions: Wrap spinners in
aria-liveregions for dynamic content - Disable interactions: Disable buttons and form elements during loading states
- Announce completion: Inform users when loading completes using screen reader announcements
- Avoid spinner-only content: Don't show just a spinner without any context
- Manage focus: Return focus to appropriate elements after loading completes
Best Practices
1. Provide Clear Context
Always indicate what is being loaded:
// Good - Clear context
<Spinner label="Loading your profile..." />
<Spinner label="Saving changes..." />
<Spinner label="Processing payment..." />
// Bad - No context
<Spinner />2. Use Appropriate Sizes
Match spinner size to the context:
// Good - Size matches context
<Button disabled>
<Spinner size="sm" color="current" /> {/* Small for buttons */}
Saving...
</Button>
<div className="min-h-screen flex items-center justify-center">
<Spinner size="lg" label="Loading application..." /> {/* Large for pages */}
</div>
// Bad - Wrong size
<Button disabled>
<Spinner size="lg" /> {/* Too large for button */}
Save
</Button>3. Match Colors Contextually
Use colors that match the action or state:
// Good - Contextual colors
<Spinner color="success" label="Syncing data..." />
<Spinner color="warning" label="Processing..." />
<Spinner color="error" label="Retrying..." />
// Good - Current color in buttons
<Button color="primary" disabled>
<Spinner size="sm" color="current" />
Submit
</Button>4. Handle Loading States Properly
Disable interactions during loading:
// Good - Disabled state
<Button disabled={isLoading}>
{isLoading ? (
<>
<Spinner size="sm" color="current" />
Loading...
</>
) : (
'Submit'
)}
</Button>
<input disabled={isLoading} />
// Bad - Interactive during loading
<Button onClick={handleSubmit}>
{isLoading && <Spinner size="sm" />}
Submit
</Button>5. Provide Timeout Fallbacks
Handle cases where loading takes too long:
const [isLoading, setIsLoading] = useState(true)
const [timedOut, setTimedOut] = useState(false)
useEffect(() => {
const timeout = setTimeout(() => {
if (isLoading) {
setTimedOut(true)
}
}, 10000) // 10 second timeout
return () => clearTimeout(timeout)
}, [isLoading])
return (
<>
{isLoading && !timedOut && <Spinner label="Loading..." />}
{timedOut && (
<div>
<p>This is taking longer than expected...</p>
<Button onClick={retry}>Retry</Button>
</div>
)}
</>
)6. Use Skeleton Screens for Better UX
For content-heavy pages, consider skeleton screens instead of spinners:
// Good - Skeleton for content areas
{isLoading ? (
<div className="space-y-4">
<div className="h-4 bg-gray-200 rounded animate-pulse" />
<div className="h-4 bg-gray-200 rounded animate-pulse w-3/4" />
</div>
) : (
<Content />
)}
// Spinner for actions
<Button disabled={isSubmitting}>
{isSubmitting && <Spinner size="sm" color="current" />}
Submit
</Button>7. Avoid Nested Loading States
Don't show multiple spinners for the same action:
// Bad - Multiple spinners
<div>
<Spinner label="Loading..." />
<Card>
<CardBody>
<Spinner label="Loading content..." />
</CardBody>
</Card>
</div>
// Good - Single spinner at appropriate level
<div>
<Spinner label="Loading..." />
</div>Troubleshooting
Spinner Not Visible
If your spinner isn't showing:
Problem: Spinner is rendered but not visible on the page.
Solutions:
- Check if parent container has sufficient height:
// Bad
<div className="h-0">
<Spinner />
</div>
// Good
<div className="min-h-[100px] flex items-center justify-center">
<Spinner />
</div>- Ensure spinner color contrasts with background:
// Bad - primary spinner on blue background
<div className="bg-blue-500">
<Spinner color="primary" />
</div>
// Good
<div className="bg-blue-500">
<Spinner color="white" />
</div>- Check z-index if spinner is behind other elements:
<div className="relative">
<Spinner className="z-50" />
</div>Animation Not Smooth
If the spinner animation is choppy or stuttering:
Problem: Performance issues causing animation jank.
Solutions:
- Reduce simultaneous animations on the page
- Check for heavy JavaScript operations blocking the main thread
- Use CSS
will-changefor optimization:
<Spinner
classNames={{
wrapper: 'will-change-transform'
}}
/>Label Text Cut Off
If label text is truncated or cut off:
Problem: Container width is too narrow for the label.
Solutions:
- Ensure parent container has sufficient width:
// Bad
<div className="w-20">
<Spinner label="Loading your profile data..." />
</div>
// Good
<div className="min-w-[200px]">
<Spinner label="Loading your profile data..." />
</div>- Use shorter, more concise labels:
// Better
<Spinner label="Loading..." />- Use custom classNames to adjust label styling:
<Spinner
label="Loading your profile data..."
classNames={{
label: 'text-xs max-w-[150px] truncate'
}}
/>Spinner in Button Misaligned
If spinner doesn't align properly with button text:
Problem: Vertical alignment issues in flex containers.
Solutions:
// Bad
<Button>
<Spinner size="sm" />
Text
</Button>
// Good - Add proper spacing and alignment
<Button className="flex items-center gap-2">
<Spinner size="sm" color="current" />
<span>Loading...</span>
</Button>Accessibility Warnings
If you're getting accessibility warnings:
Problem: Missing ARIA labels or improper usage.
Solutions:
- Always provide context:
// Bad
<Spinner />
// Good
<Spinner label="Loading data" />
// or
<Spinner aria-label="Loading user profile" />- Use proper live regions:
<div aria-live="polite">
{isLoading && <Spinner label="Loading..." />}
</div>Multiple Spinners Causing Performance Issues
If multiple spinners are slowing down your page:
Problem: Too many simultaneous animations.
Solutions:
- Show spinner at higher level instead of individual items:
// Bad - Spinner for each item
{items.map(item => (
<div key={item.id}>
{item.isLoading && <Spinner />}
{item.content}
</div>
))}
// Good - Single spinner for all items
{isLoading ? (
<Spinner label="Loading items..." />
) : (
items.map(item => <div key={item.id}>{item.content}</div>)
)}- Use loading states more strategically
- Consider skeleton screens for better performance
Related Components
- Button - Use spinners in loading buttons
- Card - Display spinners in card loading states
- Modal - Show spinners in modal dialogs during operations
- Progress - For determinate loading states with percentage
- Skeleton - Alternative loading indicator for content areas
Examples
For more examples and use cases, check out:
- Forms Guide - Using spinners in form submissions
- Data Fetching - Loading states for API calls
- Patterns - Comprehensive loading state patterns
On this page
- Import
- Usage
- Sizes
- Sizes with Labels
- Colors
- Special Colors
- Labels
- Label Colors
- Integration Examples
- In Buttons
- In Cards
- Inline Loading
- Full Page Loading
- In Input Fields
- In Lists
- Custom Styling
- Using className
- Using classNames
- Colored Backgrounds
- Conditional Loading
- Basic Toggle
- With Error Handling
- Progress Indicator
- Real-World Examples
- Form Submission
- Data Table Loading
- Infinite Scroll
- API Reference
- Spinner Props
- SpinnerSlots
- Size Reference
- Color Reference
- Accessibility
- ARIA Attributes
- Screen Reader Announcements
- Focus Management
- Best Practices for Accessibility
- Best Practices
- 1. Provide Clear Context
- 2. Use Appropriate Sizes
- 3. Match Colors Contextually
- 4. Handle Loading States Properly
- 5. Provide Timeout Fallbacks
- 6. Use Skeleton Screens for Better UX
- 7. Avoid Nested Loading States
- Troubleshooting
- Spinner Not Visible
- Animation Not Smooth
- Label Text Cut Off
- Spinner in Button Misaligned
- Accessibility Warnings
- Multiple Spinners Causing Performance Issues
- Related Components
- Examples