Placeholder block for loading states.
Introduction
Skeletons are placeholder blocks that pulse while content loads. They preserve layout shape so the interface feels stable instead of jumping when data arrives.
When to use: Show a loading stand-in for text lines, avatars, images, or card sections while data is fetching.
Compared to similar components:
- Avatar — Renders a real image, initials, or icon. Use Skeleton with rounded="full" as a temporary stand-in until the Avatar data is ready.
- Card — Groups real content. Compose Skeletons inside a Card to preview its structure during loading.
- Progress / Spinner — Communicate how far work has progressed. Prefer Skeleton when you want to preserve layout shape instead of a progress indicator.
Size skeletons with Tailwind height and width classes (h-* / w-*). There is no built-in size prop.
Use the framework selector in the site header to switch between React and Vue.
Import
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";import { Skeleton } from "@bridge-ui/react/Components/Skeleton";Basic usage
Compose multiple Skeleton blocks to mirror the layout of the content that will replace them. Control dimensions with utility classes on className (React) or class (Vue).
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-3">
<div class="flex items-center gap-3">
<Skeleton rounded="full" class="h-10 w-10" />
<div class="flex flex-1 flex-col gap-2">
<Skeleton class="h-4 w-32" />
<Skeleton class="h-3 w-24" />
</div>
</div>
<Skeleton rounded="lg" class="h-24 w-full" />
</div>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonBasic() {
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<div className="flex items-center gap-3">
<Skeleton rounded="full" className="h-10 w-10" />
<div className="flex flex-1 flex-col gap-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-24" />
</div>
</div>
<Skeleton rounded="lg" className="h-24 w-full" />
</div>
);
}
Media card
Stack a large media block with avatar, text lines, and pill-shaped actions to preview a card while its content loads.
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
</script>
<template>
<div
class="w-full max-w-md overflow-hidden rounded-xl border border-dark-200 dark:border-dark-700"
>
<Skeleton rounded="none" class="h-44 w-full" />
<div class="flex flex-col gap-4 p-4">
<div class="flex items-center gap-3">
<Skeleton rounded="full" class="h-12 w-12 shrink-0" />
<div class="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton class="h-4 w-40" />
<Skeleton class="h-3 w-28" />
</div>
</div>
<div class="flex flex-col gap-2">
<Skeleton class="h-3 w-full" />
<Skeleton class="h-3 w-full" />
<Skeleton class="h-3 w-3/4" />
</div>
<div class="flex gap-2">
<Skeleton rounded="full" class="h-8 w-20" />
<Skeleton rounded="full" class="h-8 w-24" />
</div>
</div>
</div>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonCard() {
return (
<div className="w-full max-w-md overflow-hidden rounded-xl border border-dark-200 dark:border-dark-700">
<Skeleton rounded="none" className="h-44 w-full" />
<div className="flex flex-col gap-4 p-4">
<div className="flex items-center gap-3">
<Skeleton rounded="full" className="h-12 w-12 shrink-0" />
<div className="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton className="h-4 w-40" />
<Skeleton className="h-3 w-28" />
</div>
</div>
<div className="flex flex-col gap-2">
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-3/4" />
</div>
<div className="flex gap-2">
<Skeleton rounded="full" className="h-8 w-20" />
<Skeleton rounded="full" className="h-8 w-24" />
</div>
</div>
</div>
);
}
List
Repeat a row pattern—thumbnail, text stack, and trailing control—for feed or inbox loading states.
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
const items = [0, 1, 2, 3];
</script>
<template>
<div
class="flex w-full max-w-lg flex-col divide-y divide-dark-200 dark:divide-dark-700"
>
<div
:key="index"
v-for="index in items"
class="flex items-center gap-3 py-3 first:pt-0 last:pb-0"
>
<Skeleton rounded="lg" class="h-14 w-14 shrink-0" />
<div class="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton class="h-4 w-48 max-w-full" />
<Skeleton class="h-3 w-64 max-w-full" />
<Skeleton class="h-3 w-36 max-w-full" />
</div>
<Skeleton rounded="full" class="h-8 w-8 shrink-0" />
</div>
</div>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonList() {
return (
<div className="flex w-full max-w-lg flex-col divide-y divide-dark-200 dark:divide-dark-700">
{Array.from({ length: 4 }, (_, index) => (
<div
key={index}
className="flex items-center gap-3 py-3 first:pt-0 last:pb-0"
>
<Skeleton rounded="lg" className="h-14 w-14 shrink-0" />
<div className="flex min-w-0 flex-1 flex-col gap-2">
<Skeleton className="h-4 w-48 max-w-full" />
<Skeleton className="h-3 w-64 max-w-full" />
<Skeleton className="h-3 w-36 max-w-full" />
</div>
<Skeleton rounded="full" className="h-8 w-8 shrink-0" />
</div>
))}
</div>
);
}
Page layout
Combine a page title, metric tiles, a wide content area, and a sidebar of cards for larger loading screens.
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
</script>
<template>
<div class="flex w-full flex-col gap-6">
<div class="flex flex-col gap-3">
<Skeleton class="h-8 w-72 max-w-full" />
<Skeleton class="h-4 w-96 max-w-full" />
</div>
<div class="grid gap-3 sm:grid-cols-3">
<Skeleton rounded="xl" class="h-24 w-full" />
<Skeleton rounded="xl" class="h-24 w-full" />
<Skeleton rounded="xl" class="h-24 w-full" />
</div>
<div class="grid gap-6 lg:grid-cols-[1fr_16rem]">
<div class="flex flex-col gap-3">
<Skeleton rounded="xl" class="h-56 w-full" />
<Skeleton class="h-3 w-full" />
<Skeleton class="h-3 w-full" />
<Skeleton class="h-3 w-5/6" />
<Skeleton class="h-3 w-4/5" />
</div>
<div class="flex flex-col gap-3">
<Skeleton class="h-5 w-32" />
<Skeleton rounded="lg" class="h-28 w-full" />
<Skeleton rounded="lg" class="h-28 w-full" />
<Skeleton rounded="lg" class="h-28 w-full" />
</div>
</div>
</div>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonPage() {
return (
<div className="flex w-full flex-col gap-6">
<div className="flex flex-col gap-3">
<Skeleton className="h-8 w-72 max-w-full" />
<Skeleton className="h-4 w-96 max-w-full" />
</div>
<div className="grid gap-3 sm:grid-cols-3">
<Skeleton rounded="xl" className="h-24 w-full" />
<Skeleton rounded="xl" className="h-24 w-full" />
<Skeleton rounded="xl" className="h-24 w-full" />
</div>
<div className="grid gap-6 lg:grid-cols-[1fr_16rem]">
<div className="flex flex-col gap-3">
<Skeleton rounded="xl" className="h-56 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-5/6" />
<Skeleton className="h-3 w-4/5" />
</div>
<div className="flex flex-col gap-3">
<Skeleton className="h-5 w-32" />
<Skeleton rounded="lg" className="h-28 w-full" />
<Skeleton rounded="lg" className="h-28 w-full" />
<Skeleton rounded="lg" className="h-28 w-full" />
</div>
</div>
</div>
);
}
Rounded
Use the rounded prop to control border radius. Available values: none, xs, sm, md, lg, xl, 2xl, 3xl, 4xl, and full. The default is md.
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
</script>
<template>
<div class="flex flex-wrap items-end gap-3">
<Skeleton rounded="none" class="h-12 w-12" />
<Skeleton rounded="sm" class="h-12 w-12" />
<Skeleton rounded="md" class="h-12 w-12" />
<Skeleton rounded="2xl" class="h-12 w-12" />
<Skeleton rounded="full" class="h-12 w-12" />
</div>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonRounded() {
return (
<div className="flex flex-wrap items-end gap-3">
<Skeleton rounded="none" className="h-12 w-12" />
<Skeleton rounded="sm" className="h-12 w-12" />
<Skeleton rounded="md" className="h-12 w-12" />
<Skeleton rounded="2xl" className="h-12 w-12" />
<Skeleton rounded="full" className="h-12 w-12" />
</div>
);
}
Customization
Fine-tune appearance with className (React) or class (Vue), and the classes prop to target the root element—for example, to change the pulse background color.
<script setup lang="ts">
import { Skeleton } from "@bridge-ui/vue/Components/Skeleton";
</script>
<template>
<Skeleton
class="h-4 w-48"
:classes="{
root: 'bg-primary-200 dark:bg-primary-800',
}"
/>
</template>
import { Skeleton } from "@bridge-ui/react/Components/Skeleton";
export default function SkeletonCustomization() {
return (
<Skeleton
className="h-4 w-48"
classes={{
root: "bg-primary-200 dark:bg-primary-800",
}}
/>
);
}
Accessibility
- Skeleton is decorative layout chrome. When it replaces content that is still loading, put aria-busy="true" on the container that owns the pending region, and update it when loading finishes.
- Prefer a short visible or accessible status (e.g. aria-live="polite") on the parent region so assistive technology knows content is loading.
- Do not rely on the pulse animation alone—animation is not a reliable signal for all users.
Anatomy
Skeleton renders a single empty root <div> with pulse animation and a gray background:
<div class="skeleton-root"></div>Related components
Avatar, Card, Progress, Spinner
API
| Prop | Type | Default | Description |
|---|---|---|---|
| classes | SkeletonClasses | — | Classes for root. |
| rounded | SkeletonRounded | "md" | Border radius. |