# Skeleton (/ui/components/react/skeleton)



<Playground component="skeleton" />

## Usage [#usage]

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'
```

```tsx
<Skeleton className="h-4 w-40" />
```

`Skeleton` is a low-level placeholder block. It has no fixed size — you give it dimensions with ordinary utility classes (`h-*`/`w-*`/`size-*`) — and a gentle default `rounded-md` you can reshape with any `rounded-*` (`rounded-full` for a dot, `rounded-[inherit]` to match a parent's shape, or `rounded-none` to square it off). This keeps it composable: build a line, a circle, an avatar, or a whole card preview by stacking `Skeleton`s, and they all inherit your design tokens automatically.

Its surface and shimmer highlight are both derived from `currentColor`, so a single `text-*` class recolors the whole placeholder coherently — the shimmer always matches the surface. By default it's a subtle tint of your foreground token.

Pick the animation with the `effect` prop — `shimmer` (default) glides a soft highlight across the block, `pulse` fades it in and out, and `none` renders a static placeholder. Both animations are skipped when the user prefers reduced motion.

## Examples [#examples]

### Default [#default]

A single `Skeleton` with a height, width, and radius. The default `shimmer` effect glides a highlight derived from your foreground token, so it reads on both light and dark surfaces.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonDefault() {
  return <Skeleton className="h-28 w-64 rounded-xl" />
}
```

### Effects [#effects]

Switch the animation with `effect`. `shimmer` glides a highlight across the block, `pulse` fades the whole block, and `none` stays static — useful when you want the placeholder without motion.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonEffects() {
  return (
    <div className="flex flex-wrap items-start justify-center gap-10">
      <div className="flex flex-col items-center gap-3">
        <Skeleton effect="shimmer" className="h-24 w-40 rounded-xl" />
        <span className="text-foreground-muted text-xs">shimmer</span>
      </div>
      <div className="flex flex-col items-center gap-3">
        <Skeleton effect="pulse" className="h-24 w-40 rounded-xl" />
        <span className="text-foreground-muted text-xs">pulse</span>
      </div>
      <div className="flex flex-col items-center gap-3">
        <Skeleton effect="none" className="h-24 w-40 rounded-xl" />
        <span className="text-foreground-muted text-xs">none</span>
      </div>
    </div>
  )
}
```

### Composing shapes [#composing-shapes]

There are no shape props. Reshape the same primitive with utility classes — `rounded-full` for an avatar, `size-*` for a square, a small `h-*`/`w-*` for a line.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonShapes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-6">
      <Skeleton className="size-16 rounded-full" />
      <Skeleton className="size-16 rounded-xl" />
      <Skeleton className="h-10 w-32 rounded-lg" />
      <Skeleton className="h-4 w-40 rounded-full" />
    </div>
  )
}
```

### Text lines [#text-lines]

Stack a few full-width lines and taper the last ones with fractional widths (`w-4/5`, `w-2/3`) to mimic a paragraph.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonText() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-2.5">
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-full" />
      <Skeleton className="h-4 w-4/5" />
      <Skeleton className="h-4 w-2/3" />
    </div>
  )
}
```

### Card [#card]

Compose placeholders into the layout you're loading — a media block, an avatar, and two text lines — so the skeleton matches the real content's footprint and there's no layout shift when it arrives.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonCard() {
  return (
    <div className="border-border bg-background w-full max-w-sm rounded-xl border p-4">
      <Skeleton className="aspect-video w-full rounded-lg" />
      <div className="mt-4 flex items-center gap-3">
        <Skeleton className="size-10 shrink-0 rounded-full" />
        <div className="flex flex-1 flex-col gap-2">
          <Skeleton className="h-3.5 w-2/3" />
          <Skeleton className="h-3 w-1/3" />
        </div>
      </div>
    </div>
  )
}
```

### Recoloring [#recoloring]

Surface and shimmer both come from `currentColor`, so one `text-*` class tints the whole placeholder and keeps the shimmer in sync with the surface. For a shimmer color independent of the surface, set the `--skeleton-highlight` custom property directly.

```tsx
import { Skeleton } from '@appica/ui-react/skeleton'

export default function SkeletonColors() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-4">
      <Skeleton className="size-16 rounded-xl" />
      <Skeleton className="text-primary size-16 rounded-xl" />
      <Skeleton className="text-secondary-emphasis size-16 rounded-xl" />
      <Skeleton className="text-error-emphasis size-16 rounded-xl" />
      <Skeleton className="text-success-emphasis size-16 rounded-xl" />
      <Skeleton className="text-warning-emphasis size-16 rounded-xl" />
      <Skeleton className="text-info-emphasis size-16 rounded-xl" />
      <Skeleton className="size-16 rounded-xl text-violet-500" />
    </div>
  )
}
```

### Swapping in content [#swapping-in-content]

Render skeletons while content is pending, then swap them for the real elements once it arrives. Here the avatar uses its built-in [`AvatarFallback`](/ui/components/react/avatar) to show a `Skeleton` until the photo loads, and `onLoadingStatusChange` reveals the name and role at the same moment — hit **Reload** to fetch a fresh copy and replay it. Matching each skeleton to its element's size keeps the layout stable across the transition.

```tsx
'use client'

import { useState, useEffect } from 'react'
import { Skeleton } from '@appica/ui-react/skeleton'
import { Button } from '@appica/ui-react/button'
import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar'
import { Reload } from '@appica/icons-react'

export default function SkeletonLoading() {
  const [src, setSrc] = useState<string>()
  const [loaded, setLoaded] = useState(false)
  const [nonce, setNonce] = useState(0)

  useEffect(() => {
    setLoaded(false)
    setSrc(undefined)
    // Demo only: defer the image ~2.5s so the loading skeletons stay visible.
    const id = setTimeout(() => setSrc(`/avatars/01.jpg?reload=${nonce}`), 2500)
    return () => clearTimeout(id)
  }, [nonce])

  return (
    <div className="flex w-full max-w-sm flex-col gap-4">
      <div className="flex justify-end">
        <Button size="sm" variant="outline" onClick={() => setNonce((n) => n + 1)} disabled={!loaded}>
          <Reload data-icon="start" />
          Reload
        </Button>
      </div>

      <div className="border-border bg-background flex items-center gap-3 rounded-xl border p-4">
        <Avatar size="lg" className="shrink-0 bg-transparent">
          <AvatarImage
            src={src}
            alt="Sarah Jenkins"
            onLoadingStatusChange={(status) => setLoaded(status === 'loaded')}
          />
          <AvatarFallback>
            <Skeleton className="size-full rounded-[inherit]" />
          </AvatarFallback>
        </Avatar>
        <div className="flex flex-1 flex-col gap-1.5">
          {loaded ? (
            <>
              <span className="text-foreground-intense -mb-0.5 text-sm font-semibold">Sarah Jenkins</span>
              <span className="text-foreground-muted text-xs">Product designer</span>
            </>
          ) : (
            <>
              <Skeleton className="h-4 w-2/3" />
              <Skeleton className="h-3 w-2/5" />
            </>
          )}
        </div>
      </div>
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

Because skeletons are positioned with the same logical layout utilities as the content they stand in for, a placeholder card mirrors automatically — the avatar moves to the start (right) edge and the text lines follow. The `shimmer` sweep also reverses to travel right-to-left, following the reading direction.

<RtlPreview component="skeleton" />

## API reference [#api-reference]

`Skeleton` renders a `<div>`. Every other `<div>` attribute is forwarded, so `className`, `style`, and data attributes all pass straight through.

| Prop        | <ColMinWidth width="200">Type</ColMinWidth> | Default     | <ColMinWidth width="220">Description</ColMinWidth>                                                               |
| ----------- | ------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `effect`    | <Code>'shimmer' \| 'pulse' \| 'none'</Code> | `'shimmer'` | Animation played while the placeholder is visible. All effects honor `prefers-reduced-motion`.                   |
| `className` | `string`                                    | —           | Shape, color, and radius. A `text-*` class recolors surface + shimmer; `rounded-*`/`size-*`/`h-*`/`w-*` reshape. |

The surface is `currentColor` at low opacity and the shimmer highlight is derived from `currentColor` too, so a single `text-*` class recolors both in step. The shimmer is also exposed as the `--skeleton-highlight` CSS custom property — set it on the element (e.g. `[--skeleton-highlight:var(--primary)]`) to retune the shimmer color independently of the surface.

## Accessibility [#accessibility]

* A `Skeleton` is decorative — it renders with `aria-hidden="true"` so screen readers skip the placeholder geometry rather than announcing empty boxes.
* For a loading region, announce the pending state on the **container**, not each block: set `aria-busy="true"` while data loads, and consider a polite live region (`role="status"`) that reveals the content once it arrives.
* Keep the skeleton's footprint close to the real content's so swapping it in causes no layout shift — a jump on load is its own accessibility problem.
* All animations stop under `prefers-reduced-motion: reduce` (and the library's `data-disable-animations` escape hatch), leaving a static placeholder.
