Skip to content

Skeleton ​

A placeholder that mimics the shape of content while it loads. Showing a skeleton in place of the eventual layout makes loading feel faster and avoids the jolt of content popping in — the page keeps its shape while data arrives.

Skeleton is deliberately minimal: it's an animated box you shape yourself with utility classes. Size it, round it, and arrange a few together to match whatever you're standing in for — a line of text, an avatar, a card.

Usage ​

Shape each skeleton with class — height, width, and radius. A few stacked lines stand in for a paragraph.

Text linesStacked lines of varying width.
<script setup lang="ts">
import { Skeleton } from "@dlbcodes/ui";
</script>

<template>
    <div class="flex w-full max-w-sm flex-col gap-2">
        <Skeleton class="h-4 w-3/4" />
        <Skeleton class="h-4 w-full" />
        <Skeleton class="h-4 w-5/6" />
    </div>
</template>

Shapes ​

The same component becomes a circle, a line, or a block depending on the classes you give it.

ShapesA circle, a line, and a block.
<script setup lang="ts">
import { Skeleton } from "@dlbcodes/ui";
</script>

<template>
    <div class="flex items-center gap-4">
        <!-- circle (avatar) -->
        <Skeleton class="size-12 rounded-full" />
        <!-- line -->
        <Skeleton class="h-4 w-32" />
        <!-- block -->
        <Skeleton class="h-16 w-24 rounded-xl" />
    </div>
</template>

Composing a card ​

Combine skeletons to mirror the layout you're loading — here, an avatar and a few lines of text, the classic "loading card."

Loading cardAn avatar circle with text lines.
<script setup lang="ts">
import { Skeleton } from "@dlbcodes/ui";
</script>

<template>
    <div
        class="w-full max-w-sm rounded-2xl border border-border-subtle bg-bg-raised p-5"
    >
        <div class="flex items-center gap-3">
            <Skeleton class="size-12 shrink-0 rounded-full" />
            <div class="flex flex-1 flex-col gap-2">
                <Skeleton class="h-4 w-1/2" />
                <Skeleton class="h-3 w-1/3" />
            </div>
        </div>
        <div class="mt-4 flex flex-col gap-2">
            <Skeleton class="h-3 w-full" />
            <Skeleton class="h-3 w-full" />
            <Skeleton class="h-3 w-2/3" />
        </div>
    </div>
</template>

In practice ​

Show skeletons while data is loading, then swap in the real content. Mark the loading container aria-busy="true" so assistive tech knows content is on its way.

Loading then contentToggle between the skeleton and the loaded state.
<script setup lang="ts">
import { ref } from "vue";
import { Skeleton, Button } from "@dlbcodes/ui";

const loading = ref(true);
</script>

<template>
    <div class="flex w-full max-w-sm flex-col gap-4">
        <Button
            variant="outline"
            size="sm"
            class="w-fit"
            @click="loading = !loading"
        >
            {{ loading ? "Show content" : "Show skeleton" }}
        </Button>

        <div v-if="loading" class="flex flex-col gap-2" aria-busy="true">
            <Skeleton class="h-5 w-1/2" />
            <Skeleton class="h-4 w-full" />
            <Skeleton class="h-4 w-5/6" />
        </div>
        <div v-else class="flex flex-col gap-2">
            <div class="text-lg font-semibold text-text-primary">
                Loaded title
            </div>
            <div class="text-sm text-text-secondary">
                This is the real content that appears once loading finishes.
            </div>
        </div>
    </div>
</template>

Appearance ​

A skeleton is filled with the theme's --accent color and pulses. It has the theme's medium corner radius by default, so it follows --radius. Pass your own radius to change it, such as rounded-full for an avatar placeholder. On a very light page the fill is subtle. If you need more contrast, pass class="bg-border". See Theming.

Props ​

PropTypeDefaultDescription
classstring—Classes that shape the skeleton — height, width, radius (e.g. h-4 w-32, size-12 rounded-full).

Skeleton intentionally takes only class. You control its shape entirely with utilities, so it can stand in for any element.

Accessibility ​

  • The skeleton is aria-hidden="true" — it's a purely visual placeholder, so it's hidden from assistive technology (a screen reader has nothing meaningful to announce for an empty placeholder).
  • Mark the container that's loading with aria-busy="true" while the skeletons are shown, so assistive tech knows real content is coming. Remove it once the content has loaded.