Spinner

A loading spinner component that indicates an ongoing process or data fetching operation.

Usage

Basic Spinner

import { Spinner } from "@prisma-docs/eclipse";

export function MyComponent() {
  return <Spinner />;
}

Live Example:

Custom Size

import { Spinner } from "@prisma-docs/eclipse";

export function CustomSizeSpinner() {
  return (
    <div className="flex items-center gap-4">
      <Spinner className="size-4" />
      <Spinner className="size-6" />
      <Spinner className="size-8" />
      <Spinner className="size-12" />
    </div>
  );
}

Live Example:

Error State

import { Spinner } from "@prisma-docs/eclipse";

export function ErrorSpinner() {
  return (
    <div className="flex items-center gap-4">
      <Spinner error />
      <Spinner error className="size-6" />
      <Spinner error className="size-8" />
    </div>
  );
}

Live Example:

Error State with Message

import { Spinner } from "@prisma-docs/eclipse";

export function ErrorSpinnerWithMessage() {
  return (
    <div className="flex items-center gap-2">
      <Spinner error className="size-5" />
      <span className="text-sm text-foreground-error">
        Failed to load. Retrying...
      </span>
    </div>
  );
}

Live Example:

Failed to load. Retrying...

In Button

import { Spinner } from "@prisma-docs/eclipse";
import { Button } from "@prisma-docs/eclipse";

export function ButtonWithSpinner() {
  return (
    <Button disabled className="flex items-center gap-0.75">
      <Spinner className="mr-2" />
      <span></span>Loading...</span>
    </Button>
  );
}

Live Example:

Centered in Container

import { Spinner } from "@prisma-docs/eclipse";

export function CenteredSpinner() {
  return (
    <div className="flex items-center justify-center h-32">
      <Spinner className="size-8" />
    </div>
  );
}

Live Example:

With Text

import { Spinner } from "@prisma-docs/eclipse";

export function SpinnerWithText() {
  return (
    <div className="flex items-center gap-2">
      <Spinner />
      <span className="text-sm text-foreground-neutral-weak">
        Loading your data...
      </span>
    </div>
  );
}

Live Example:

Loading your data...

Full Page Loading

import { Spinner } from "@prisma-docs/eclipse";

export function FullPageLoader() {
  return (
    <div className="fixed inset-0 flex items-center justify-center bg-background-default/80 backdrop-blur-sm">
      <div className="flex flex-col items-center gap-4">
        <Spinner className="size-12" />
        <p className="text-sm text-foreground-neutral-weak">
          Loading application...
        </p>
      </div>
    </div>
  );
}

Spinner Props

The Spinner component accepts all standard SVG element props:

  • error - Display spinner in error state with different icon (boolean, optional)
  • className - Additional CSS classes (optional)
  • role - ARIA role (default: "status")
  • aria-label - ARIA label (default: "Loading")
  • All standard SVG attributes are supported

Features

  • ✅ Built on Lucide React's Loader2 icon
  • ✅ Smooth rotation animation
  • ✅ Accessible with proper ARIA attributes
  • ✅ Customizable size via Tailwind classes
  • ✅ Built-in error state for failure scenarios
  • ✅ Lightweight and performant
  • ✅ Works in any context (buttons, cards, pages)
  • ✅ Eclipse design system styling

Best Practices

  • Use appropriate size for the context (small in buttons, larger for page loading)
  • Always pair with descriptive text or aria-label for accessibility
  • Consider placement - centered for full-page, inline for components
  • Show spinners only when necessary - avoid overuse
  • Disable interactive elements while loading (buttons, forms, etc.)
  • Provide feedback when loading completes
  • Consider showing skeleton loaders for better UX on longer operations
  • Use appropriate z-index for overlay spinners
  • Ensure sufficient contrast with background

Common Patterns

Async Button

import { Spinner } from "@prisma-docs/eclipse";
import { Button } from "@prisma-docs/eclipse";
import { useState } from "react";

export function AsyncButton() {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    try {
      await fetch('/api/data');
    } finally {
      setLoading(false);
    }
  };

  return (
    <Button onClick={handleClick} disabled={loading}>
      {loading && <Spinner className="mr-2" />}
      {loading ? 'Submitting...' : 'Submit'}
    </Button>
  );
}

Loading State with Error Handling

import { Spinner } from "@prisma-docs/eclipse";

export function DataDisplay({ data, loading, error }) {
  if (loading) {
    return (
      <div className="flex items-center justify-center h-64">
        <Spinner className="size-8" />
      </div>
    );
  }

  if (error) {
    return (
      <div className="flex items-center justify-center gap-2 text-foreground-error">
        <Spinner error className="size-6" />
        <span>Error loading data</span>
      </div>
    );
  }

  return <div>{/* render data */}</div>;
}

Error State with Retry

import { Spinner } from "@prisma-docs/eclipse";
import { Button } from "@prisma-docs/eclipse";
import { useState } from "react";

export function DataFetcherWithRetry() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'error' | 'success'>('idle');

  const handleFetch = async () => {
    setStatus('loading');
    try {
      await fetch('/api/data');
      setStatus('success');
    } catch (error) {
      setStatus('error');
    }
  };

  if (status === 'loading') {
    return (
      <div className="flex items-center gap-2">
        <Spinner />
        <span>Loading...</span>
      </div>
    );
  }

  if (status === 'error') {
    return (
      <div className="flex items-center gap-2">
        <Spinner error />
        <span className="text-foreground-error">Failed to load.</span>
        <Button size="lg" onClick={handleFetch}>Retry</Button>
      </div>
    );
  }

  return <div>{/* render data */}</div>;
}

Inline Loading

import { Spinner } from "@prisma-docs/eclipse";

export function InlineLoader({ loading, children }) {
  if (loading) {
    return (
      <span className="inline-flex items-center gap-2">
        <Spinner className="size-4" />
        <span className="text-sm text-foreground-neutral-weak">Loading...</span>
      </span>
    );
  }

  return children;
}

Card Loading

import { Spinner } from "@prisma-docs/eclipse";
import { Card, CardContent } from "@prisma-docs/eclipse";

export function LoadingCard() {
  return (
    <Card>
      <CardContent className="flex items-center justify-center h-48">
        <div className="flex flex-col items-center gap-3">
          <Spinner className="size-8" />
          <p className="text-sm text-foreground-neutral-weak">
            Loading content...
          </p>
        </div>
      </CardContent>
    </Card>
  );
}

Overlay Loading

import { Spinner } from "@prisma-docs/eclipse";

export function LoadingOverlay({ loading, children }) {
  return (
    <div className="relative">
      {children}
      {loading && (
        <div className="absolute inset-0 flex items-center justify-center bg-background-default/80 backdrop-blur-sm rounded-lg">
          <Spinner className="size-8" />
        </div>
      )}
    </div>
  );
}

Conditional Loading

import { Spinner } from "@prisma-docs/eclipse";

export function ConditionalSpinner({ isLoading, message = "Loading..." }) {
  if (!isLoading) return null;

  return (
    <div className="flex items-center gap-2 p-4">
      <Spinner />
      <span className="text-sm">{message}</span>
    </div>
  );
}

Accessibility

  • Has role="status" to announce loading state to screen readers
  • Includes aria-label="Loading" by default for screen reader users
  • Should be paired with visible text when possible
  • Disable interactive elements while loading
  • Announce completion of loading operations
  • Use proper color contrast
  • Consider aria-live regions for dynamic content updates
  • Provide meaningful loading messages

Size Guidelines

  • Extra Small (size-4): Inline text, small buttons, badges
  • Small (size-6): Regular buttons, form inputs, list items
  • Medium (size-8): Cards, sections, modal content
  • Large (size-12): Full page loading, main content areas
  • Custom: Use arbitrary values like className="w-16 h-16" for specific needs

Performance

The Spinner component is optimized for performance:

  • Uses CSS animations (GPU-accelerated)
  • Minimal re-renders
  • No JavaScript animation loops
  • Lightweight SVG icon
  • Can be conditionally rendered without performance impact

Integration Examples

With React Query

import { Spinner } from "@prisma-docs/eclipse";
import { useQuery } from "@tanstack/react-query";

export function DataFetcher() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
  });

  if (isLoading) {
    return (
      <div className="flex justify-center p-8">
        <Spinner className="size-8" />
      </div>
    );
  }

  if (error) return <div>Error: {error.message}</div>;

  return <div>{/* render data */}</div>;
}

With Next.js Loading UI

import { Spinner } from "@prisma-docs/eclipse";

// app/loading.tsx
export default function Loading() {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <div className="flex flex-col items-center gap-4">
        <Spinner className="size-12" />
        <p className="text-foreground-neutral-weak">Loading page...</p>
      </div>
    </div>
  );
}

With Form Submission

import { Spinner } from "@prisma-docs/eclipse";
import { Button } from "@prisma-docs/eclipse";
import { useState } from "react";

export function ContactForm() {
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setSubmitting(true);
    try {
      await submitForm();
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <Button type="submit" disabled={submitting}>
        {submitting ? (
          <>
            <Spinner className="mr-2" />
            Sending...
          </>
        ) : (
          'Send Message'
        )}
      </Button>
    </form>
  );
}

Styling

The Spinner uses Eclipse design system principles:

  • Animation: Smooth 1s linear rotation
  • Default Size: 1rem (size-4)
  • Accessibility: Proper ARIA attributes
  • Customization: Fully customizable via className

Override styles as needed:

<Spinner className="size-8 text-blue-500 animate-spin-slow" />

On this page