The Skeleton component stands in for content that has not arrived yet. Instead of an empty area or a spinning circle, the page already shows the shape of what is coming, so nothing jumps when the data lands.
Example
Import the component and give it the size of the thing it replaces. The default variant is a rectangle.
<script>
import { Skeleton } from "theui-svelte";
</script>
<Skeleton />Variants
The variant prop takes rect (default), circle for avatars, and text for lines of writing.
<Skeleton variant="rect" class="h-16" />
<Skeleton variant="circle" />
<Skeleton variant="text" />Text Lines
With variant="text", the lines prop draws a paragraph. The last line is shorter, the way a real paragraph ends. Each line takes the line height of its parent, so the placeholder matches the text that will replace it.
<Skeleton variant="text" lines={3} />
<!-- The line height comes from the parent -->
<div class="text-2xl">
<Skeleton variant="text" lines={2} />
</div>Animation
The animation prop takes pulse (default), wave for a shimmer that travels across, and none for a still placeholder.
<Skeleton animation="pulse" class="h-10" />
<Skeleton animation="wave" class="h-10" />
<Skeleton animation="none" class="h-10" />Size and Shape
Size comes from your own classes, so any width, height or aspect ratio works. The rounded prop sets the corner radius of the rect and text variants.
<Skeleton class="h-8 w-40" rounded="full" />
<Skeleton class="aspect-video w-full max-w-sm" rounded="xl" />
<Skeleton variant="circle" class="size-8" />
<Skeleton variant="circle" class="size-12" />
<Skeleton variant="circle" class="size-16" />Card Placeholder
Put a few skeletons together to stand in for a whole card. Match the real layout as closely as you can, so the page does not move when the content arrives.
<div class="w-full max-w-sm rounded-xl bg-secondary p-4">
<Skeleton animation="wave" class="mb-4 aspect-video w-full" rounded="lg" />
<div class="flex items-center gap-3">
<Skeleton variant="circle" animation="wave" class="size-10" />
<div class="grow">
<Skeleton variant="text" animation="wave" lines={2} />
</div>
</div>
</div>Swapping in Content
Show the skeleton while the data is on its way and the real content once it arrives. Mark the area that is loading with aria-busy, so assistive technology knows the content is not final.
<script>
let loading = $state(true)
</script>
<div aria-busy={loading}>
{#if loading}
<Skeleton variant="text" lines={3} />
{:else}
<p>The real content is here now.</p>
{/if}
</div>Customization
The class prop sets the size and the colour of the placeholder, and lineClasses styles every line of the text variant.
<Skeleton class="h-10 bg-brand-500/20" />
<Skeleton class="h-10 bg-info-200 dark:bg-info-900" animation="wave" />
<Skeleton variant="text" lines={3} lineClasses="h-3 rounded-full" />Accessibility
A skeleton is decoration, so it is hidden from screen readers with aria-hidden. What matters is the area around it: put aria-busy="true" on the container while it loads, and remove it once the content is there. Announce the result in a live region if the wait is long. For people who prefer reduced motion both animations stop by themselves, leaving a plain placeholder.