# Prefer Skeletons Over Spinners

**SHOULD** · **ID:** `interactions-ibelick-loading-skeletons` · **Category:** interactions
**Source:** [@Ibelick](https://www.ui-skills.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-ibelick-loading-skeletons

> SHOULD: Use structural skeletons for loading states instead of spinners. Skeletons show content shape, reduce perceived wait time, and prevent layout shift.

Use skeleton placeholders instead of spinners for loading states to reduce perceived load time

The load-bearing word upstream is *structural*: a skeleton earns its keep by mirroring the shape of the content that is about to arrive, which reserves the layout and cuts perceived wait. A generic shimmering block is just a spinner with extra steps. This is a SHOULD, not a MUST — a spinner is still fine for a short, unpredictable wait with no known shape.

## Bad — do not do this

`interactions-ibelick-loading-skeletons-bad`

```tsx
import { useState, useEffect } from 'react';

export function IbelickLoadingSkeletonsBad() {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const timer = setTimeout(() => setLoading(false), 2000);
    return () => clearTimeout(timer);
  }, []);

  if (loading) {
    return (
      <div className="space-y-4">
        <div className="flex items-center justify-center p-12 bg-muted rounded-lg">
          <div className="size-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
        </div>
        <p className="text-xs text-destructive">
          Spinner gives no indication of what's loading or how long it will take
        </p>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex gap-4 p-4 bg-muted rounded-lg">
        <div className="size-12 bg-primary/20 rounded-full" />
        <div className="flex-1">
          <p className="font-medium">John Doe</p>
          <p className="text-sm text-muted-foreground">Software Engineer</p>
        </div>
      </div>
      <p className="text-xs text-muted-foreground">Content loaded</p>
    </div>
  );
}
```

## Good — do this

`interactions-ibelick-loading-skeletons-good`

```tsx
import { useState, useEffect } from 'react';

export function IbelickLoadingSkeletonsGood() {
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const timer = setTimeout(() => setLoading(false), 2000);
    return () => clearTimeout(timer);
  }, []);

  if (loading) {
    return (
      <div className="space-y-4">
        <div className="flex gap-4 p-4 bg-muted rounded-lg animate-pulse">
          <div className="size-12 bg-muted-foreground/20 rounded-full" />
          <div className="flex-1 space-y-2">
            <div className="h-4 bg-muted-foreground/20 rounded w-1/3" />
            <div className="h-3 bg-muted-foreground/20 rounded w-1/2" />
          </div>
        </div>
        <p className="text-xs text-success">
          Skeleton shows content structure - reduces perceived load time
        </p>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      <div className="flex gap-4 p-4 bg-muted rounded-lg">
        <div className="size-12 bg-primary/20 rounded-full" />
        <div className="flex-1">
          <p className="font-medium">John Doe</p>
          <p className="text-sm text-muted-foreground">Software Engineer</p>
        </div>
      </div>
      <p className="text-xs text-muted-foreground">Content loaded</p>
    </div>
  );
}
```

## References

- [ibelick/ui-skills — baseline-ui](https://github.com/ibelick/ui-skills)
- [Skeleton Screens](https://www.lukew.com/ff/entry.asp?1797)
