# Skeleton

> Loading placeholders whose shimmer is one light passing diagonally over the whole screen, plus presets and a Swap that eases from the skeleton's height to the content's while it cross-fades.

- Type: Component (feedback)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/skeleton
- Markdown: https://goodui.dev/native/components/skeleton.md
- Registry item: https://goodui.dev/native/r/skeleton.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native skeleton, expo skeleton loader, shimmer placeholder, loading placeholder, content loader, skeleton screen, placeholder shimmer react native

## When to use

- The first load of a screen or list whose layout you know: feeds, inboxes, profiles, product grids.
- Content that takes longer than about 300 ms, where a blank area would look broken.
- Skeleton.Swap whenever the placeholder and the content occupy the same place.

## When not to use

- A button or form submit that is waiting: return the promise from button's onPress and it shows its own spinner.
- Refreshing content that is already on screen: use pull-to-refresh and keep the old content visible.
- Progress you can measure, like an upload: use progress-ring.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/skeleton.json
npx expo install --fix
```

The component lands in `components/goodui-native/skeleton/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { Skeleton } from "@/components/goodui-native/skeleton/skeleton";

export function Inbox({ messages }: { messages?: Message[] }) {
  return (
    <Skeleton.Swap
      loading={!messages}
      loadedAnnouncement={messages ? `${messages.length} messages` : undefined}
      skeleton={
        <>
          <Skeleton.ListRow trailing />
          <Skeleton.ListRow trailing />
          <Skeleton.ListRow trailing />
        </>
      }
    >
      {messages?.map((m) => <MessageRow key={m.id} message={m} />)}
    </Skeleton.Swap>
  );
}
```

## Examples

### Primitives

```tsx
<Skeleton.Circle size={48} />
<Skeleton.Line size="title2" width="50%" />
<Skeleton.Line size="subhead" lines={3} />
<Skeleton width={120} height={44} radius={14} />
```

### Presets

```tsx
<Skeleton.Profile />
<Skeleton.Card mediaHeight={180} />
<Skeleton.ListRow avatar="square" lines={3} />
```

### Quieter animation

```tsx
<Skeleton.ListRow animation="pulse" />
<Skeleton.Card animation="none" />
```

## API reference

### Skeleton

`import { Skeleton } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `width` | `DimensionValue` | – | Width in points or percent. Default `"100%"`. |
| `height` | `number` | – | Height in points. Default 16. |
| `radius` | `number` | – | Corner radius. Defaults to the theme's `sm` radius. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. Under reduced motion `shimmer` becomes a slow pulse. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonLine

`import { SkeletonLine } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"caption" \| "footnote" \| "subhead" \| "body" \| "title3" \| "title2" \| "title1" \| "largeTitle"` | `"body"` | Matches the text it stands in for: each line takes the size's line height, the bar sits inside it. |
| `lines` | `number` | `1` | Number of lines. Default 1. |
| `width` | `DimensionValue` | `"100%"` | Width of every line but the last. Default `"100%"`. |
| `lastLineWidth` | `DimensionValue` | `"60%"` | Width of the last line when there is more than one, so a paragraph ends ragged. Default `"60%"`. |
| `align` | `"left" \| "center"` | `"left"` | Line alignment inside the block. Default `"left"`. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonCircle

`import { SkeletonCircle } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `number` | `40` | Diameter in points. Default 40. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonListRow

`import { SkeletonListRow } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `avatar` | `"circle" \| "square" \| false` | `"circle"` | Leading avatar shape, or `false` for none. Default `"circle"`. |
| `lines` | `number` | `2` | Text lines next to the avatar, 1 to 3. Default 2. |
| `trailing` | `boolean` | `false` | A short trailing value, like a time or an amount. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonCard

`import { SkeletonCard } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `media` | `boolean` | `true` | Image placeholder on top. Default `true`. |
| `mediaHeight` | `number` | `160` | Height of the image placeholder. Default 160. |
| `lines` | `number` | `2` | Body lines under the title. Default 2. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonProfile

`import { SkeletonProfile } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `stats` | `boolean` | `true` | A row of three stat placeholders under the bio. Default `true`. |
| `animation` | `"pulse" \| "none" \| "shimmer"` | – | How the placeholder signals loading. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

### SkeletonSwap

`import { SkeletonSwap } from "@/components/goodui-native/skeleton/skeleton";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` (required) | `boolean` | – | While true the skeleton shows; when it turns false the content cross-fades in. |
| `skeleton` (required) | `ReactNode` | – | Placeholder shaped like the content, built from the Skeleton primitives or presets. |
| `children` (required) | `ReactNode` | – | The real content. It mounts when loading ends. |
| `loadingLabel` | `string` | – | Screen reader label while loading. Same as `labels.loading`. |
| `loadedAnnouncement` | `string` | – | Announced to screen readers when loading ends, for example "3 new messages". |
| `labels` | `Partial<SkeletonLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |

## Accessibility

- Placeholders are hidden from screen readers.
- Skeleton.Swap is one element labelled loadingLabel (default 'Loading') with accessibilityState busy while loading.
- Pass loadedAnnouncement to announce the result when loading ends.
- Skeleton.Line takes the line height of the text size it stands in for, so the layout matches at every size.
- On the web Skeleton.Swap is a busy progressbar named loadingLabel while loading.

## Motion

- One clock drives every skeleton on screen: the shimmer is a single light that crosses the whole layout diagonally, then pauses.
- Skeleton.Swap fades the placeholder out on top, the content rises 6 pt into place on the smooth spring, and the box eases between the two heights.
- Reduced motion: the shimmer becomes a slow opacity pulse, the content only fades in and the height changes at once.

## Haptics

- None. Loading is not a commit.

## Performance

- The shared clock runs on the UI thread only while at least one animated skeleton is mounted.
- Each placeholder measures its window position once per layout change, not per frame.
- Use a handful of rows that fill the screen, not a full page of them.

## Notes for AI

- Build the placeholder from the same layout as the content: same paddings, same text sizes in Skeleton.Line.
- Use Skeleton.Swap instead of a ternary, so the switch cross-fades without a jump.
- Do not add your own shimmer or gradient; set animation='pulse' or 'none' for a quieter look.
- Skeleton.X and the named exports (SkeletonLine, SkeletonCard, ...) are the same components.

## Related

- [Pull to refresh](https://goodui.dev/native/components/pull-to-refresh.md): A scroll view whose refresh indicator is a drop that stretches as you pull, snaps round with a bounce and a haptic past the threshold, spins while onRefresh runs and ends on a check or an error.
- [Progress ring](https://goodui.dev/native/components/progress-ring.md): A circular progress ring that fills on a spring, laps itself past 100 % with a gap that lifts the lap off the ring, and bounces once with a success haptic when it completes.
- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.

## Guidance for AI tools

Skeleton: Loading placeholders whose shimmer is one light passing diagonally over the whole screen, plus presets and a Swap that eases from the skeleton's height to the content's while it cross-fades. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
