Skip to content
omaris

Data Display

Skeleton

Skeleton placeholder.

import { Skeleton } from 'omaris'
Learn

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, text lines for copy, block for 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 translate only, 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 loading prop 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-10 as 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 one label to one skeleton that stands for the whole region. Skeletons are aria-hidden by default.
  • Use animation="pulse" or none on 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)
  • pulse
  • none
shape
  • text
  • block (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
shimmer
pulse
none
shape

Defaults to 'block'

SkeletonShape
text
block
circle
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