# Skeleton

Static loading placeholder and a delayed, minimum-visible swap into real content.

- Package: `@mielui/svelte`
- Component version: `2.1.0`
- Depends on Mielui components: _internal/utils
- Shared utilities: components/_internal/theme-changes, components/_internal/motion-loop, utils.cn, transition

## Install

~~~~sh
pnpm dlx @mielui/svelte add skeleton
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### Skeleton

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| variant | "default" \| "shimmer" \| undefined | 'default' | No | No | `shimmer` sweeps a highlight across the placeholder. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| w | number \| undefined | - | No | No | Width, in `unit`. |
| h | number \| undefined | - | No | No | Height, in `unit`. |
| unit | "px" \| "rem" \| "em" \| "%" \| "vh" \| "vw" \| "vmin" \| "vmax" \| "ch" \| "ex" \| "cm" \| "mm" \| "in" \| "pt" \| "pc" \| undefined | 'px' | No | No | CSS unit for `w` and `h`. |



### SkeletonSwap

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| ready | boolean | - | Yes | No | Shows the content when true and the skeleton when false. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |
| skeleton | Snippet<[]> \| undefined | - | No | No | Custom placeholder. Defaults to text lines. |
| lines | number \| undefined | 3 | No | No | Number of placeholder lines. |
| lineHeight | number \| undefined | 21 | No | No | Height of a text line in pixels. |
| barHeight | number \| undefined | 9 | No | No | Height of each placeholder bar in pixels. |
| reserve | number \| undefined | - | No | No | Height to hold in pixels so the swap does not shift the layout. |
| delay | number \| undefined | 120 | No | No | Milliseconds to wait before showing the skeleton, so fast loads skip it. |
| minVisible | number \| undefined | 380 | No | No | Shortest time in milliseconds the skeleton stays once shown. |
| label | string \| undefined | - | No | No | Accessible name of the content. With it, the swap announces when the content arrives. |
| labels | SkeletonSwapLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| class | (string & (ClassValue \| null)) \| undefined | - | No | No | Classes added to the element. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import Skeleton from './skeleton.svelte';
import SkeletonSwap from './skeleton-swap.svelte';

export type SkeletonProps = {
    /** `shimmer` sweeps a highlight across the placeholder. */
    variant?: 'default' | 'shimmer';
    /** Content rendered inside. */
    children?: Snippet;
    /** Classes added to the element. */
    class?: string;
    /** Width, in `unit`. */
    w?: number;
    /** Height, in `unit`. */
    h?: number;
    /** CSS unit for `w` and `h`. */
    unit?:
        | 'px'
        | 'rem'
        | 'em'
        | '%'
        | 'vh'
        | 'vw'
        | 'vmin'
        | 'vmax'
        | 'ch'
        | 'ex'
        | 'cm'
        | 'mm'
        | 'in'
        | 'pt'
        | 'pc';
};

export type SkeletonSwapLabels = {
    /** Announcement once the content arrives. Receives `label`. */
    loaded?: (label: string) => string;
};

export type SkeletonSwapProps = {
    /** Shows the content when true and the skeleton when false. */
    ready: boolean;
    /** Content rendered inside. */
    children?: Snippet;
    /** Custom placeholder. Defaults to text lines. */
    skeleton?: Snippet;
    /** Number of placeholder lines. */
    lines?: number;
    /** Height of a text line in pixels. */
    lineHeight?: number;
    /** Height of each placeholder bar in pixels. */
    barHeight?: number;
    /** Height to hold in pixels so the swap does not shift the layout. */
    reserve?: number;
    /** Milliseconds to wait before showing the skeleton, so fast loads skip it. */
    delay?: number;
    /** Shortest time in milliseconds the skeleton stays once shown. */
    minVisible?: number;
    /** Accessible name of the content. With it, the swap announces when the content arrives. */
    label?: string;
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: SkeletonSwapLabels;
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children'>;

export { Skeleton, SkeletonSwap };
~~~~

## Examples

### Card

~~~~svelte
<script lang="ts">
    import { Skeleton } from '@mielui/svelte/components/skeleton';
</script>

<div class="flex flex-col gap-3">
    <Skeleton w={280} h={160} />
    <Skeleton w={200} h={20} />
    <Skeleton w={150} h={16} />
</div>
~~~~

### Circle

~~~~svelte
<script lang="ts">
    import { Skeleton } from '@mielui/svelte/components/skeleton';
</script>

<Skeleton w={100} h={100} class="rounded-full" />
~~~~

### Rectangle

~~~~svelte
<script lang="ts">
    import { Skeleton } from '@mielui/svelte/components/skeleton';
</script>

<Skeleton w={280} h={160} />
~~~~

### Reloading

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { SkeletonSwap } from '@mielui/svelte/components/skeleton';
    import { onDestroy } from 'svelte';

    let ready = $state(true);
    let timer: ReturnType<typeof setTimeout> | undefined;

    function reload() {
        clearTimeout(timer);
        ready = false;
        timer = setTimeout(() => {
            ready = true;
        }, 1200);
    }

    onDestroy(() => {
        clearTimeout(timer);
    });
</script>

<div class="flex w-full max-w-sm flex-col gap-4">
    <SkeletonSwap {ready} lines={3} lineHeight={24} label="Workspace summary">
        {#if ready}
            <p class="text-sm leading-6 text-foreground-muted">
                Your workspace has 12 active projects. Three are ready for review, and the next team
                check-in is on Friday.
            </p>
        {/if}
    </SkeletonSwap>
    <Button variant="secondary" onclick={reload} disabled={!ready}>Reload summary</Button>
</div>
~~~~

### Shimmer

~~~~svelte
<script lang="ts">
    import { Skeleton } from '@mielui/svelte/components/skeleton';
</script>

<div class="w-64 space-y-3" role="status" aria-label="Loading profile">
    <Skeleton variant="shimmer" class="size-12 rounded-full" />
    <Skeleton variant="shimmer" class="h-4 w-full" />
    <Skeleton variant="shimmer" class="h-4 w-2/3" />
    <span class="sr-only">Loading profile…</span>
</div>
~~~~

### Timing

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Skeleton, SkeletonSwap } from '@mielui/svelte/components/skeleton';
    import { onDestroy } from 'svelte';

    let ready = $state(true);
    let timer: ReturnType<typeof setTimeout> | undefined;

    function load(duration: number) {
        clearTimeout(timer);
        ready = false;
        timer = setTimeout(() => {
            ready = true;
        }, duration);
    }

    onDestroy(() => {
        clearTimeout(timer);
    });
</script>

<div class="flex w-full max-w-sm flex-col gap-4">
    <SkeletonSwap {ready} lines={2} barHeight={12} reserve={56} delay={200} minVisible={600}>
        {#if ready}
            <p class="text-sm leading-6 text-foreground-muted">
                Three invoices are overdue. The oldest is from Kestrel Logistics.
            </p>
        {/if}
    </SkeletonSwap>
    <Skeleton w={12} h={0.75} unit="rem" />
    <div class="flex gap-2">
        <Button
            variant="outline"
            onclick={() => {
                load(100);
            }}
        >
            Load in 100 ms
        </Button>
        <Button
            variant="outline"
            onclick={() => {
                load(1500);
            }}
        >
            Load in 1.5 s
        </Button>
    </div>
</div>
~~~~

For the rendered reference, visit [/docs/components/skeleton](/docs/components/skeleton).
