Skeleton
A placeholder shape shown while content loads, so the layout is in place before the data.
import { Skeleton } from '@/components/skeleton'
export default function Example() {
return (
<div className="flex items-center gap-4">
<Skeleton className="size-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-64" />
<Skeleton className="h-4 w-40" />
</div>
</div>
)
}<script setup lang="ts">
import Skeleton from '@/components/skeleton.vue'
</script>
<template>
<div class="flex items-center gap-4">
<Skeleton class="size-12 rounded-full" />
<div class="space-y-2">
<Skeleton class="h-4 w-64" />
<Skeleton class="h-4 w-40" />
</div>
</div>
</template>Installation
Copy these files into the folder where you keep your components. They import each other with relative paths, so the folder can live anywhere. The packages they rely on are listed on the Installation page.
import clsx from 'clsx'
import type React from 'react'
export function Skeleton({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
return (
<div
aria-hidden="true"
{...props}
className={clsx(className, 'animate-pulse rounded-md bg-zinc-950/5 motion-reduce:animate-none dark:bg-white/10')}
/>
)
}<script setup lang="ts">
</script>
<template>
<div aria-hidden="true" class="animate-pulse rounded-md bg-zinc-950/5 motion-reduce:animate-none dark:bg-white/10" />
</template>Component API
| Prop | Default | Description |
|---|---|---|
Skeletonrenders a <div> element | ||
| This component does not expose any component-specific props. | ||
Examples
Basic example
Size each shape with width and height classes. Round it with rounded-full for an avatar; the default radius suits lines of text:
import { Skeleton } from '@/components/skeleton'
export default function Example() {
return (
<div className="flex items-center gap-4">
<Skeleton className="size-12 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-4 w-64" />
<Skeleton className="h-4 w-40" />
</div>
</div>
)
}<script setup lang="ts">
import Skeleton from '@/components/skeleton.vue'
</script>
<template>
<div class="flex items-center gap-4">
<Skeleton class="size-12 rounded-full" />
<div class="space-y-2">
<Skeleton class="h-4 w-64" />
<Skeleton class="h-4 w-40" />
</div>
</div>
</template>Card
Mirror the layout of the card that will replace it, so nothing moves when the content arrives:
<div className="w-full max-w-sm rounded-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10">
<Skeleton className="h-32 w-full rounded-lg" />
<Skeleton className="mt-4 h-5 w-3/4" />
<Skeleton className="mt-2 h-4 w-full" />
<Skeleton className="mt-2 h-4 w-5/6" />
</div><template>
<div class="w-full max-w-sm rounded-xl p-6 ring-1 ring-zinc-950/5 dark:ring-white/10">
<Skeleton class="h-32 w-full rounded-lg" />
<Skeleton class="mt-4 h-5 w-3/4" />
<Skeleton class="mt-2 h-4 w-full" />
<Skeleton class="mt-2 h-4 w-5/6" />
</div>
</template>Table rows
Repeat a row of shapes for a list. Keep the count short: the skeleton hints at the destination, it does not have to fill the screen:
<div className="w-full divide-y divide-zinc-950/5 dark:divide-white/5">
{[0, 1, 2, 3].map((row) => (
<div key={row} className="flex items-center gap-6 py-4">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-48" />
<Skeleton className="ml-auto h-4 w-16" />
</div>
))}
</div><template>
<div class="w-full divide-y divide-zinc-950/5 dark:divide-white/5">
<div v-for="row in 4" :key="row" class="flex items-center gap-6 py-4">
<Skeleton class="h-4 w-32" />
<Skeleton class="h-4 w-48" />
<Skeleton class="ml-auto h-4 w-16" />
</div>
</div>
</template>Accessibility
Each shape is hidden from assistive tech, so a skeleton alone says nothing to a screen reader. Mark the region that is loading with aria-busy="true", add a line with the sr-only class, “Loading transactions” for example, and drop both when the content arrives. The pulse stops when the system asks for reduced motion.