Skeleton
Pre-alphaThe registry and the CLI are not published yet.Roadmap

Skeleton

Animated placeholder for loading states.

Feed

Installation

npx axiom add skeleton
pnpm dlx axiom add skeleton
yarn dlx axiom add skeleton
bun x axiom add skeleton

Installs: react-native-reanimated.

The shared animation clock lives in use-skeleton.ts. Colors come from skeleton.default.default.{background,highlight} in the component tokens.

Usage

import { Skeleton } from '@/components/ui/skeleton';

<Skeleton width={48} height={48} radius="full" />
<Skeleton width="60%" height={16} />

Props

Skeleton

PropTypeDefaultDescription
widthDimensionValue'100%'Width in points or percent. Match the content it replaces.
heightDimensionValue16Height. For text, use the line height of the text variant it stands for.
radiusRadiusToken'sm'Corner radius from the tokens. full for avatars.
variant'shimmer' | 'pulse' | 'none''shimmer'Animation. pulse fades in and out, none is static. Reduce motion forces none.
loadingbooleantrueWhen false, renders children instead of the placeholder.
childrenReactNodeThe real content, shown once loading is false.
styleStyleProp<ViewStyle>Extra styles for the placeholder.

Skeleton.Text

PropTypeDefaultDescription
linesnumber3Number of lines. The last one is shorter, like a real paragraph.
variantTextVariant'body'Uses the line height of this Text variant, so the placeholder has the same height as the text.
animation'shimmer' | 'pulse' | 'none''shimmer'Animation of the lines.
loadingbooleantrueWhen false, renders children instead of the lines.

Every skeleton on screen shares one animation clock, so shimmers stay in sync. Skeletons are hidden from screen readers; announce the loading state once on the container.

Use cases

List placeholder

Repeat a skeleton row with the shape of the real row. The layout doesn’t jump when data arrives.

Contacts

function ContactRowSkeleton() {
	return (
		<Item>
			<Item.Leading>
				<Skeleton width={40} height={40} radius="full" />
			</Item.Leading>
			<Item.Content>
				<Skeleton width="50%" height={17} />
				<Skeleton width="30%" height={13} />
			</Item.Content>
		</Item>
	);
}

{
	isLoading
		? Array.from({ length: 6 }, (_, i) => <ContactRowSkeleton key={i} />)
		: contacts.map(renderContact);
}

Content already partly known

The title comes from the previous screen, only the body is loading. Skeleton.Text stands in for the paragraph.

Night trains of Europe12 min read
<Title>{params.title}</Title>
<Skeleton loading={!article} height={180} radius="lg">
  <Image source={article?.cover} />
</Skeleton>
{article ? <Text>{article.body}</Text> : <Skeleton.Text lines={3} />}

Grid of cards

Same number of tiles as the first page of results, with the aspect ratio of the images.

Shop

<FlatList
	numColumns={2}
	data={isLoading ? placeholders : products}
	renderItem={({ item }) =>
		isLoading ? <ProductCardSkeleton /> : <ProductCard product={item} />
	}
/>