Skip to content
Primitives

Skeleton

Sharp sweeping placeholder.

● LIVE
npx shadcn@latest add https://okibi.cndr.dev/r/skeleton.json

Installation

Install the theme first, then add the component:

npx shadcn@latest add https://okibi.cndr.dev/r/skeleton.json

Copy the source from the Code tab above into components/ui/skeleton.tsx. It uses the cn helper and the okibi theme tokens — install the theme first if you haven't.

Usage

import { Skeleton } from "@/components/ui/skeleton"

<Skeleton className="h-8 w-48" />

Examples

Card

Layer several Skeleton blocks into a card silhouette — image, avatar, lines — for a real loading placeholder.

● LIVE

Accessible loading

Wrap the placeholders in role="status" + aria-busy with an sr-only Loading label so assistive tech announces the pending state.

● LIVE
Loading…

Accessibility

  • A Skeleton is an unopinionated div and is silent to assistive tech on its own — it conveys no loading state by itself.
  • Wrap the loading region in a container with role='status' and aria-busy='true', and include an sr-only 'Loading…' label so the wait is announced.
  • Mark the placeholder shapes aria-hidden so screen readers hear the status label, not a pile of empty boxes.
  • Visibility comes from the border-field boundary (the dim surface-2 fill alone is ~1.1:1), clearing the WCAG 1.4.11 non-text 3:1 bar in both modes.
  • The highlight sweep is reduced-motion-gated and rests at the legible bordered fill, which stays readable on its own.

Guidelines

Do

  • Size and shape skeletons with Tailwind utilities on className (h-8 w-48) — that className is the only knob the component takes.
  • Mirror the real content's layout so the swap-in doesn't shift the page.
  • Announce the wait at the region level with role='status' + aria-busy rather than per-skeleton.

Don't

  • Don't expect a skeleton to announce loading — it's a styled div; the role='status' wrapper does that.
  • Don't drop the border-field boundary; without it the dim fill is effectively invisible.
  • Don't reach for opacity pulsing — the on-brand motion is an opaque highlight sweep that never fades toward the card.

On this page