Build beautiful apps
Start

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

Small
Medium
Large

Colors

Choose from various color schemes to match your design:

Default
Primary
Secondary
Success
Warning
Error

Special Colors

The white and current colors adapt to their context:

White
Current Color

Labels

Add descriptive text to provide context:

Loading...
Processing your request...
Fetching data...

Label Colors

Customize the label color independently from the spinner:

Foreground
Primary
Secondary
Success
Warning
Error

Integration Examples

In Buttons

Use spinners to indicate loading states in buttons:

In Cards

Center spinners in card components for loading states:

Loading content...
Fetching data...

Inline Loading

Display spinners inline with text:

Loading data...
Syncing files...
Processing transaction...

Full Page Loading

Create full-page loading overlays:

Loading application...

In Input Fields

Show loading states next to form inputs:

In Lists

Indicate loading for list items:

User Profile
Settings
Notifications

Custom Styling

Using className

Add custom classes to the spinner:

50% Opacity
Scaled

Using classNames

Customize individual slots:

Custom Spinner
Large Custom

Colored Backgrounds

Spinners on different colored backgrounds:

On Blue
On Gradient
On Dark

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

PropTypeDefaultDescription
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
labelstring-Optional label text to display below the spinner
labelColor'foreground' | 'primary' | 'secondary' | 'success' | 'warning' | 'error''foreground'The color of the label text
classNamestring-Additional CSS classes for the base element
classNamesSpinnerSlots-Custom classes for individual slots

SpinnerSlots

The classNames prop accepts an object with the following keys:

SlotDescription
baseThe main wrapper element that contains the spinner and label
wrapperThe container for the spinning circles
circle1The first (outer) circle of the spinner animation
circle2The second (inner) circle of the spinner animation
labelThe label text element

Size Reference

SizeSpinner DimensionsBest Use Case
sm16x16pxInline with text, buttons, small UI elements
md32x32pxDefault size, cards, forms, moderate spaces
lg48x48pxFull page loading, large containers, prominent feedback

Color Reference

ColorUsage
defaultNeutral loading states, general purpose
primaryMain actions, important loading states
secondarySecondary actions, alternative loading states
successSuccessful operations, data syncing
warningWarning states, caution needed
errorError recovery, retry operations
whiteDark backgrounds, colored containers
currentInherit 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")

Loading...

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

  1. Always provide context: Use either a visible label or aria-label to describe what's loading
  2. Use appropriate live regions: Wrap spinners in aria-live regions for dynamic content
  3. Disable interactions: Disable buttons and form elements during loading states
  4. Announce completion: Inform users when loading completes using screen reader announcements
  5. Avoid spinner-only content: Don't show just a spinner without any context
  6. 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:

  1. 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>
  1. 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>
  1. 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:

  1. Reduce simultaneous animations on the page
  2. Check for heavy JavaScript operations blocking the main thread
  3. Use CSS will-change for 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:

  1. 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>
  1. Use shorter, more concise labels:
// Better
<Spinner label="Loading..." />
  1. 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:

  1. Always provide context:
// Bad
<Spinner />
 
// Good
<Spinner label="Loading data" />
// or
<Spinner aria-label="Loading user profile" />
  1. 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:

  1. 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>)
)}
  1. Use loading states more strategically
  2. Consider skeleton screens for better performance
  • 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: