Skeleton
An animated placeholder that holds a content's shape while it loads.
Usage
Skeleton is a low-level placeholder block. It has no fixed size - you give it dimensions with ordinary utility classes (h-*/w-*/size-*) - and a gentle default rounded-md you can reshape with any rounded-* (rounded-full for a dot, rounded-[inherit] to match a parent's shape, or rounded-none to square it off). This keeps it composable: build a line, a circle, an avatar, or a whole card preview by stacking Skeletons, and they all inherit your design tokens automatically.
Its surface and shimmer highlight are both derived from currentColor, so a single text-* class recolors the whole placeholder coherently - the shimmer always matches the surface. By default it's a subtle tint of your foreground token.
Pick the animation with the effect prop - shimmer (default) glides a soft highlight across the block, pulse fades it in and out, and none renders a static placeholder. Both animations are skipped when the user prefers reduced motion.
Examples
Default
A single Skeleton with a height, width, and radius. The default shimmer effect glides a highlight derived from your foreground token, so it reads on both light and dark surfaces.
Effects
Switch the animation with effect. shimmer glides a highlight across the block, pulse fades the whole block, and none stays static - useful when you want the placeholder without motion.
Composing shapes
There are no shape props. Reshape the same primitive with utility classes - rounded-full for an avatar, size-* for a square, a small h-*/w-* for a line.
Text lines
Stack a few full-width lines and taper the last ones with fractional widths (w-4/5, w-2/3) to mimic a paragraph.
Card
Compose placeholders into the layout you're loading (a media block, an avatar, and two text lines) so the skeleton matches the real content's footprint and there's no layout shift when it arrives.
Recoloring
Surface and shimmer both come from currentColor, so one text-* class tints the whole placeholder and keeps the shimmer in sync with the surface. For a shimmer color independent of the surface, set the --skeleton-highlight custom property directly.
Swapping in content
Render skeletons while content is pending, then swap them for the real elements once it arrives. Here the avatar uses its built-in AvatarFallback to show a Skeleton until the photo loads, and onLoadingStatusChange reveals the name and role at the same moment - hit Reload to fetch a fresh copy and replay it. Matching each skeleton to its element's size keeps the layout stable across the transition.
RTL
Every Appica UI component supports right-to-left layouts out of the box. Set the dir attribute on a container (commonly your <html> element) so CSS logical properties resolve correctly, and wrap your tree in DirectionProvider so direction-aware behavior follows the same direction.
Because skeletons are positioned with the same logical layout utilities as the content they stand in for, a placeholder card mirrors automatically - the avatar moves to the start (right) edge and the text lines follow. The shimmer sweep also reverses to travel right-to-left, following the reading direction.
API reference
Skeleton renders a <div>. Every other <div> attribute is forwarded, so className, style, and data attributes all pass straight through.
The surface is currentColor at low opacity and the shimmer highlight is derived from currentColor too, so a single text-* class recolors both in step. The shimmer is also exposed as the --skeleton-highlight CSS custom property - set it on the element (e.g. [--skeleton-highlight:var(--primary)]) to retune the shimmer color independently of the surface.
Accessibility
- A
Skeletonis decorative - it renders witharia-hidden="true"so screen readers skip the placeholder geometry rather than announcing empty boxes. - For a loading region, announce the pending state on the container, not each block: set
aria-busy="true"while data loads, and consider a polite live region (role="status") that reveals the content once it arrives. - Keep the skeleton's footprint close to the real content's so swapping it in causes no layout shift - a jump on load is its own accessibility problem.
- All animations stop under
prefers-reduced-motion: reduce(and the library'sdata-disable-animationsescape hatch), leaving a static placeholder.