Data Display
Skeleton
Skeleton placeholder.
import { Skeleton } from 'omaris' Examples
Shapes
Skeletons should match the shape of what is coming, or the page jumps when the real thing lands.
<script lang="ts"> import { Skeleton } from 'omaris';</script><div class="flex w-full max-w-md items-start gap-4"> <Skeleton shape="circle" class="size-10" /> <div class="flex min-w-0 flex-1 flex-col gap-2"> <Skeleton shape="text" class="w-1/3" /> <Skeleton shape="text" lines={3} /> </div></div> Animation
The shimmer rests for the last part of its cycle, so a wall of skeletons does not strobe. none respects a user who has asked for less motion.
shimmer pulse none <script lang="ts"> import { Skeleton } from 'omaris';</script><div class="flex w-full max-w-md flex-col gap-4"> <div class="flex flex-col gap-1.5"> <code class="text-label-sm text-muted-foreground">shimmer</code> <Skeleton animation="shimmer" class="h-10 w-full" /> </div> <div class="flex flex-col gap-1.5"> <code class="text-label-sm text-muted-foreground">pulse</code> <Skeleton animation="pulse" class="h-10 w-full" /> </div> <div class="flex flex-col gap-1.5"> <code class="text-label-sm text-muted-foreground">none</code> <Skeleton animation="none" class="h-10 w-full" /> </div></div> When to use it
Use it for
- The shape of what is coming:
shape="circle"for an avatar,textlines for copy,blockfor a chart or an image. Lay them out in the real component's slots so nothing jumps when the data lands. - A paragraph:
lines={3}repeats the row with a short last line. - A whole list or card grid while its first page loads. The shimmer uses
translateonly, so a screenful stays at 60fps.
Not for
- A load with a known fraction, like an upload or export → Progress with
value. - A wait inside a control, like a pressed button or a refreshing table → the
loadingprop on Button and Table. The table draws its own skeleton rows. - Nothing to show once loading ends → Empty, with the one action that fills it.
- A brief wait on content already on screen → the indeterminate Progress bar at the top of the region.
Do
- Match the real size: the same
size-10as the avatar, the same line count as the copy, the same footer. Keep the real layout around it. - Mark the region
aria-busy, or give onelabelto one skeleton that stands for the whole region. Skeletons arearia-hiddenby default. - Use
animation="pulse"ornoneon a page with many of them. Both stop under reduced motion.
Don't
- Give every block a
label. "Loading" twelve times is noise. - Leave a skeleton up after an error. A failed load is
tone="destructive"on Empty. - Show one for data that arrives in under a couple of hundred milliseconds. The flash of grey is worse than the blank.
Quick reference
animation shimmer(default)pulsenone
shape textblock(default)circle
API
Skeleton
Skeleton placeholder.
shimmer sweeps a highlight across the block; pulse just breathes. The sweep rides on translate only — a compositor property — so a screenful of skeletons stays at 60fps and costs no layout. Both stop dead under prefers-reduced-motion, leaving a plain grey block: a loading state should never be the thing that makes someone sick.
Skeletons are decorative by default (aria-hidden), because a screen reader announcing "Loading" once per grey box is noise. Put one label on the skeleton that stands for the whole region — or better, mark the region itself aria-busy — and leave the rest silent.
import { Skeleton } from 'omaris' Props
animation Defaults to 'shimmer'
SkeletonAnimation shimmerpulsenone
shape Defaults to 'block'
SkeletonShape textblockcircle
lines Defaults to 1
number Number of lines. Above one, the block repeats with a short last line so it reads as a paragraph.
label string Announce this skeleton as a live loading region under this name. Use it once per region — not once per block.
class string