BetaFeedback

Skeleton

The shape of what has not arrived yet.

Overview

The shape of what has not arrived yet.

First example

<Skeleton width={140} height={20} />
<Skeleton width={48} height={48} radius="full" />

Anatomy

<Skeleton />

One node and no slots. A placeholder is a rectangle; there is nothing inside it to name. A paragraph of them is three of these in a Column — composition doing what a lines={3} prop would otherwise hard-code, including the last line being shorter, which is the only reason the paragraph reads as a paragraph.

Usage

A block, sized by the caller:

<Skeleton width={140} height={20} />
<Skeleton width={48} height={48} radius="full" />

A paragraph:

<Column gap={8}>
  <Skeleton height={12} />
  <Skeleton height={12} />
  <Skeleton height={12} width="60%" />
</Column>

As a gate, which is what isLoading is for:

<Skeleton isLoading={!user} height={20} width={140}>
  <Typography>{user?.name}</Typography>
</Skeleton>

A row of one, mirroring the layout it stands in for:

<Row gap={12} alignItems="center">
  <Skeleton width={40} height={40} radius="full" />
  <Column gap={6} flex={1}>
    <Skeleton height={12} width="50%" />
    <Skeleton height={12} width="80%" />
  </Column>
</Row>

Root props

SkeletonProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNoWhat the block stands in for, rendered once `isLoading` is `false`. A skeleton with no children is a block that never resolves, which is the right thing for a placeholder whose content is mounted elsewhere.
radiusRadiusKey | undefinedNoThe corner. `full` is the circle an avatar placeholder needs, and it is why this is a prop rather than the caller's `borderRadius` — a circle is a shape the vocabulary names, and it has to survive a theme redrawing every corner in the library.
colorstring | undefinedNoA raw tint (`'#7c3aed'`), never a token (R7). There is one thing to colour on a block, so it lands on the block.
isLoadingboolean | undefinedNo`false` swaps the block for `children`. It is the prop that makes the component a gate rather than a shape you mount and unmount around your own content: ```tsx <Skeleton isLoading={!user} height={20} width={140}> <Typography>{user?.name}</Typography> </Skeleton> ```
animationboolean | undefinedNo`false` freezes the block at full opacity and mounts no worklet. A boolean rather than the reference implementation's `'shimmer' | 'pulse' | 'none'`: a shimmer is a gradient sweeping across the block, a gradient needs `react-native-svg`, and that is an optional peer a component in the core cannot require. One animation, so there is nothing for a name to choose between.
styleStyleProp<ViewStyle>No

Slots

This component is standalone and exposes no public slot.

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

accessibilityElementsHidden and importantForAccessibility="no-hide-descendants" are set. A block stands in for content that is not there; announcing its absence is busy on whatever region is loading, not a stop on each rectangle. Both stay overridable (R9).

Migration from legacy

Legacyv1
<Skeleton width={140} height={20}/><Skeleton width={140} height={20} />
<Skeleton circle size={40} /><Skeleton width={40} height={40} radius="full" />
<Skeleton lines={3} />three <Skeleton> in a <Column>
<Skeleton animated={false} /><Skeleton animation={false} />

Implementation notes

Animation

One pulse: the block breathes between full opacity and a half, a second each way, eased in and out. animation={false} freezes it at full and mounts no worklet — the branch renders a plain View, so a long list frozen for a screenshot costs nothing.

No shimmer, where the reference implementation's default is one. A shimmer is a gradient sweeping across the block; a gradient needs react-native-svg, and that is an optional peer a component in the fifteen-component core cannot require. One animation, so there is nothing for a name to choose between — animation is a boolean rather than the reference implementation's 'shimmer' | 'pulse' | 'none'.