Skeleton
A placeholder component showing loading state while content loads
Controls
Toggle loading state and animation type for all demos below.
Basic Skeletons
Simple skeleton shapes and sizes.
Sizes
Shapes
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { Skeleton } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2">
12 <div class="space-y-4 rounded-lg border p-4">
13 <h3 class="font-semibold">Sizes</h3>
14 <div class="space-y-3">
15 <div class="flex items-center gap-4">
16 <span class="w-12 text-xs text-muted-foreground">xs</span>
17 <Skeleton {animation} size="xs" width="full" visible={isLoading} />
18 </div>
19 <div class="flex items-center gap-4">
20 <span class="w-12 text-xs text-muted-foreground">sm</span>
21 <Skeleton {animation} size="sm" width="full" visible={isLoading} />
22 </div>
23 <div class="flex items-center gap-4">
24 <span class="w-12 text-xs text-muted-foreground">md</span>
25 <Skeleton {animation} size="md" width="full" visible={isLoading} />
26 </div>
27 <div class="flex items-center gap-4">
28 <span class="w-12 text-xs text-muted-foreground">lg</span>
29 <Skeleton {animation} size="lg" width="full" visible={isLoading} />
30 </div>
31 <div class="flex items-center gap-4">
32 <span class="w-12 text-xs text-muted-foreground">xl</span>
33 <Skeleton {animation} size="xl" width="full" visible={isLoading} />
34 </div>
35 </div>
36 </div>
37
38 <div class="space-y-4 rounded-lg border p-4">
39 <h3 class="font-semibold">Shapes</h3>
40 <div class="flex flex-wrap gap-4">
41 <div class="text-center">
42 <Skeleton {animation} shape="default" class="h-16 w-24" visible={isLoading} />
43 <span class="mt-1 block text-xs text-muted-foreground">default</span>
44 </div>
45 <div class="text-center">
46 <Skeleton {animation} shape="circle" class="h-16 w-16" visible={isLoading} />
47 <span class="mt-1 block text-xs text-muted-foreground">circle</span>
48 </div>
49 <div class="text-center">
50 <Skeleton {animation} shape="square" class="h-16 w-24" visible={isLoading} />
51 <span class="mt-1 block text-xs text-muted-foreground">square</span>
52 </div>
53 <div class="text-center">
54 <Skeleton {animation} shape="pill" class="h-8 w-24" visible={isLoading} />
55 <span class="mt-1 block text-xs text-muted-foreground">pill</span>
56 </div>
57 </div>
58 </div>
59</div>Variants
Different visual styles for skeletons.
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { Skeleton } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-4 md:grid-cols-4">
12 <div class="space-y-2 rounded-lg border p-4">
13 <Skeleton {animation} variant="default" size="lg" width="full" visible={isLoading} />
14 <span class="text-xs text-muted-foreground">default</span>
15 </div>
16 <div class="space-y-2 rounded-lg border p-4">
17 <Skeleton {animation} variant="primary" size="lg" width="full" visible={isLoading} />
18 <span class="text-xs text-muted-foreground">primary</span>
19 </div>
20 <div class="space-y-2 rounded-lg border p-4">
21 <Skeleton {animation} variant="secondary" size="lg" width="full" visible={isLoading} />
22 <span class="text-xs text-muted-foreground">secondary</span>
23 </div>
24 <div class="space-y-2 rounded-lg border p-4">
25 <Skeleton {animation} variant="accent" size="lg" width="full" visible={isLoading} />
26 <span class="text-xs text-muted-foreground">accent</span>
27 </div>
28</div>Width Shortcuts
Convenient width presets.
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { Skeleton } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="space-y-3 rounded-lg border p-4">
12 <div class="flex items-center gap-4">
13 <span class="w-16 text-xs text-muted-foreground">full</span>
14 <Skeleton {animation} size="md" width="full" visible={isLoading} />
15 </div>
16 <div class="flex items-center gap-4">
17 <span class="w-16 text-xs text-muted-foreground">half</span>
18 <Skeleton {animation} size="md" width="half" visible={isLoading} />
19 </div>
20 <div class="flex items-center gap-4">
21 <span class="w-16 text-xs text-muted-foreground">third</span>
22 <Skeleton {animation} size="md" width="third" visible={isLoading} />
23 </div>
24 <div class="flex items-center gap-4">
25 <span class="w-16 text-xs text-muted-foreground">quarter</span>
26 <Skeleton {animation} size="md" width="quarter" visible={isLoading} />
27 </div>
28 <div class="flex items-center gap-4">
29 <span class="w-16 text-xs text-muted-foreground">200px</span>
30 <Skeleton {animation} size="md" width="200px" visible={isLoading} />
31 </div>
32</div>Multiple Skeletons
Render multiple skeletons with stagger animation.
Count = 4
Varied Widths
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { Skeleton } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2">
12 <div class="space-y-4 rounded-lg border p-4">
13 <h3 class="font-semibold">Count = 4</h3>
14 <Skeleton
15 {animation}
16 count={4}
17 size="sm"
18 width="full"
19 delay={100}
20 visible={isLoading}
21 />
22 </div>
23
24 <div class="space-y-4 rounded-lg border p-4">
25 <h3 class="font-semibold">Varied Widths</h3>
26 <div class="space-y-2">
27 <Skeleton {animation} size="sm" width="90%" delay={0} visible={isLoading} />
28 <Skeleton {animation} size="sm" width="full" delay={50} visible={isLoading} />
29 <Skeleton {animation} size="sm" width="75%" delay={100} visible={isLoading} />
30 <Skeleton {animation} size="sm" width="60%" delay={150} visible={isLoading} />
31 </div>
32 </div>
33</div>Skeleton Text
Preset for text paragraphs with natural line widths.
Default (3 lines)
5 Lines (Uniform)
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { SkeletonText } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2">
12 <div class="space-y-4 rounded-lg border p-4">
13 <h3 class="font-semibold">Default (3 lines)</h3>
14 {#if isLoading}
15 <SkeletonText {animation} />
16 {:else}
17 <p class="text-sm">
18 This is the actual content that would be displayed after loading.
19 It contains multiple lines of text to demonstrate the skeleton placeholder.
20 </p>
21 {/if}
22 </div>
23
24 <div class="space-y-4 rounded-lg border p-4">
25 <h3 class="font-semibold">5 Lines (Uniform)</h3>
26 {#if isLoading}
27 <SkeletonText {animation} lines={5} varied={false} />
28 {:else}
29 <p class="text-sm">Content loaded successfully!</p>
30 {/if}
31 </div>
32</div>Skeleton Avatar
Avatar placeholders with optional text.
Avatar Only
With Text
Extra Small
Extra Large
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { SkeletonAvatar } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-4">
12 <div class="space-y-4 rounded-lg border p-4">
13 <h3 class="text-sm font-semibold">Avatar Only</h3>
14 {#if isLoading}
15 <SkeletonAvatar {animation} size="lg" />
16 {:else}
17 <div class="flex h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground">
18 JD
19 </div>
20 {/if}
21 </div>
22
23 <div class="space-y-4 rounded-lg border p-4">
24 <h3 class="text-sm font-semibold">With Text</h3>
25 {#if isLoading}
26 <SkeletonAvatar {animation} withText />
27 {:else}
28 <div class="flex items-center gap-3">
29 <div class="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-sm text-primary-foreground">
30 JD
31 </div>
32 <div>
33 <p class="font-medium">John Doe</p>
34 <p class="text-sm text-muted-foreground">john@example.com</p>
35 </div>
36 </div>
37 {/if}
38 </div>
39
40 <div class="space-y-4 rounded-lg border p-4">
41 <h3 class="text-sm font-semibold">Extra Small</h3>
42 <SkeletonAvatar {animation} size="xs" withText visible={isLoading} />
43 </div>
44
45 <div class="space-y-4 rounded-lg border p-4">
46 <h3 class="text-sm font-semibold">Extra Large</h3>
47 <SkeletonAvatar {animation} size="xl" withText textLines={3} visible={isLoading} />
48 </div>
49</div>Skeleton Card
Card layout placeholders with configurable sections.
Full Card
No Image
Minimal
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { SkeletonCard } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
12 <div>
13 <h3 class="mb-2 text-sm font-semibold">Full Card</h3>
14 {#if isLoading}
15 <SkeletonCard {animation} />
16 {:else}
17 <div class="rounded-lg border bg-card p-4 shadow-sm">
18 <div class="mb-4 flex items-center gap-3">
19 <div class="h-10 w-10 rounded-full bg-primary"></div>
20 <div>
21 <p class="font-medium">Card Title</p>
22 <p class="text-sm text-muted-foreground">Subtitle</p>
23 </div>
24 </div>
25 <p class="text-sm">Card content goes here.</p>
26 </div>
27 {/if}
28 </div>
29
30 <div>
31 <h3 class="mb-2 text-sm font-semibold">No Image</h3>
32 <SkeletonCard
33 {animation}
34 withImage={false}
35 visible={isLoading}
36 />
37 </div>
38
39 <div>
40 <h3 class="mb-2 text-sm font-semibold">Minimal</h3>
41 <SkeletonCard
42 {animation}
43 withImage={false}
44 withAvatar={false}
45 withFooter={false}
46 bodyLines={4}
47 visible={isLoading}
48 />
49 </div>
50</div>Skeleton Table
Table placeholder with configurable rows and columns.
Default Table (5 rows, 4 columns)
Compact Table (3 rows, 2 columns)
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { SkeletonTable } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="space-y-6">
12 <div>
13 <h3 class="mb-2 font-semibold">Default Table (5 rows, 4 columns)</h3>
14 {#if isLoading}
15 <SkeletonTable {animation} />
16 {:else}
17 <div class="w-full overflow-hidden rounded-lg border">
18 <table class="w-full">
19 <thead class="bg-muted/50">
20 <tr>
21 <th class="px-4 py-3 text-left text-sm font-medium">Name</th>
22 <th class="px-4 py-3 text-left text-sm font-medium">Email</th>
23 <th class="px-4 py-3 text-left text-sm font-medium">Role</th>
24 <th class="px-4 py-3 text-left text-sm font-medium">Status</th>
25 </tr>
26 </thead>
27 <tbody class="divide-y">
28 {#each Array(5) as _}
29 <tr>
30 <td class="px-4 py-3 text-sm">John Doe</td>
31 <td class="px-4 py-3 text-sm">john@example.com</td>
32 <td class="px-4 py-3 text-sm">Admin</td>
33 <td class="px-4 py-3 text-sm">Active</td>
34 </tr>
35 {/each}
36 </tbody>
37 </table>
38 </div>
39 {/if}
40 </div>
41
42 <div>
43 <h3 class="mb-2 font-semibold">Compact Table (3 rows, 2 columns)</h3>
44 <SkeletonTable
45 {animation}
46 rows={3}
47 columns={2}
48 visible={isLoading}
49 />
50 </div>
51</div>Real-world Examples
Common loading patterns in applications.
Social Post
Product List
1
2<script lang="ts">
3 import { SkeletonPrimitives } from "@kareyes/aether";
4
5 const { Skeleton, SkeletonText, SkeletonAvatar } = SkeletonPrimitives;
6
7 let isLoading = $state(true);
8 let animation = "pulse";
9</script>
10
11<div class="grid gap-6 md:grid-cols-2">
12 <!-- Social Post -->
13 <div class="space-y-4 rounded-lg border p-4">
14 <h3 class="font-semibold">Social Post</h3>
15 {#if isLoading}
16 <div class="space-y-4">
17 <SkeletonAvatar {animation} withText size="md" />
18 <SkeletonText {animation} lines={2} />
19 <Skeleton {animation} class="aspect-video w-full" />
20 <div class="flex gap-4">
21 <Skeleton {animation} shape="pill" size="sm" width="60px" />
22 <Skeleton {animation} shape="pill" size="sm" width="60px" />
23 <Skeleton {animation} shape="pill" size="sm" width="60px" />
24 </div>
25 </div>
26 {:else}
27 <div class="space-y-4">
28 <div class="flex items-center gap-3">
29 <div class="h-10 w-10 rounded-full bg-primary"></div>
30 <div>
31 <p class="font-medium">Jane Smith</p>
32 <p class="text-xs text-muted-foreground">2 hours ago</p>
33 </div>
34 </div>
35 <p class="text-sm">Just finished building this awesome skeleton component!</p>
36 <div class="aspect-video w-full rounded-lg bg-muted"></div>
37 <div class="flex gap-4 text-sm text-muted-foreground">
38 <span>42 likes</span>
39 <span>12 comments</span>
40 <span>Share</span>
41 </div>
42 </div>
43 {/if}
44 </div>
45
46 <!-- Product List -->
47 <div class="space-y-4 rounded-lg border p-4">
48 <h3 class="font-semibold">Product List</h3>
49 {#if isLoading}
50 <div class="space-y-3">
51 {#each Array(3) as _, i}
52 <div class="flex gap-4">
53 <Skeleton {animation} shape="default" class="h-16 w-16 shrink-0" />
54 <div class="flex-1 space-y-2">
55 <Skeleton {animation} size="sm" width="70%" delay={i * 50} />
56 <Skeleton {animation} size="xs" width="50%" delay={i * 50 + 25} />
57 <Skeleton {animation} size="sm" width="30%" delay={i * 50 + 50} />
58 </div>
59 </div>
60 {/each}
61 </div>
62 {:else}
63 <div class="space-y-3">
64 {#each ["Widget Pro", "Gadget Plus", "Device Ultra"] as product}
65 <div class="flex gap-4">
66 <div class="h-16 w-16 shrink-0 rounded-lg bg-muted"></div>
67 <div class="flex-1">
68 <p class="font-medium">{product}</p>
69 <p class="text-xs text-muted-foreground">In stock</p>
70 <p class="text-sm font-semibold">$99.00</p>
71 </div>
72 </div>
73 {/each}
74 </div>
75 {/if}
76 </div>
77</div>Features
- ✅ Multiple Variants: default, primary, secondary, accent
- ✅ Animation Options: pulse, shimmer, wave, none
- ✅ Shape Presets: default (rounded), circle, square, pill
- ✅ Size Presets: xs, sm, md, lg, xl, full
- ✅ Width Shortcuts: full, half, third, quarter, or custom values
- ✅ Multiple Count: Render multiple skeletons with stagger animation
- ✅ Preset Components: SkeletonText, SkeletonAvatar, SkeletonCard, SkeletonTable
- ✅ TypeScript Support: Complete type definitions
- ✅ Visibility Control: Toggle skeleton display
<script>
import {
Skeleton,
SkeletonText,
SkeletonAvatar,
SkeletonCard,
SkeletonTable
} from "@kareyes/aether";
</script>
Basic Usage
Simple Skeleton
<Skeleton width="200px" height="20px" />
With Size Presets
<Skeleton size="sm" width="full" />
<Skeleton size="md" width="half" />
<Skeleton size="lg" width="200px" />
Circle Avatar Placeholder
<Skeleton shape="circle" class="h-12 w-12" />
Animation Options
Pulse (Default)
<Skeleton animation="pulse" size="md" width="full" />
Shimmer Effect
<Skeleton animation="shimmer" size="md" width="full" />
Wave Effect
<Skeleton animation="wave" size="md" width="full" />
No Animation
<Skeleton animation="none" size="md" width="full" />
Variants
<Skeleton variant="default" size="lg" width="full" />
<Skeleton variant="primary" size="lg" width="full" />
<Skeleton variant="secondary" size="lg" width="full" />
<Skeleton variant="accent" size="lg" width="full" />
Multiple Skeletons
Render multiple skeletons with optional stagger animation:
<Skeleton count={4} size="sm" width="full" delay={100} />
Width Shortcuts
<Skeleton width="full" /> <!-- 100% -->
<Skeleton width="half" /> <!-- 50% -->
<Skeleton width="third" /> <!-- 33.333% -->
<Skeleton width="quarter" /> <!-- 25% -->
<Skeleton width="200px" /> <!-- Custom -->
Visibility Control
Toggle skeleton visibility based on loading state:
<script>
let isLoading = $state(true);
</script>
<Skeleton visible={isLoading} size="md" width="full" />
Preset Components
SkeletonText
Paragraph placeholder with natural varying line widths:
<!-- Default: 3 lines with varied widths -->
<SkeletonText />
<!-- Custom line count -->
<SkeletonText lines={5} />
<!-- Uniform widths -->
<SkeletonText lines={4} varied={false} />
<!-- Custom animation -->
<SkeletonText animation="shimmer" lines={3} />
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
lines |
number |
3 |
Number of text lines |
gap |
string |
"0.5rem" |
Gap between lines |
animation |
"pulse" | "shimmer" | "wave" | "none" |
"pulse" |
Animation type |
varied |
boolean |
true |
Vary line widths for natural appearance |
SkeletonAvatar
Avatar placeholder with optional accompanying text:
<!-- Avatar only -->
<SkeletonAvatar size="lg" />
<!-- Avatar with text lines -->
<SkeletonAvatar withText size="md" />
<!-- Custom text lines -->
<SkeletonAvatar withText textLines={3} size="xl" />
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
size |
"xs" | "sm" | "md" | "lg" | "xl" |
"md" |
Avatar size |
animation |
"pulse" | "shimmer" | "wave" | "none" |
"pulse" |
Animation type |
withText |
boolean |
false |
Show accompanying text lines |
textLines |
number |
2 |
Number of text lines (when withText is true) |
SkeletonCard
Card layout placeholder with configurable sections:
<!-- Full card with all sections -->
<SkeletonCard />
<!-- Card without image -->
<SkeletonCard withImage={false} />
<!-- Minimal card -->
<SkeletonCard
withImage={false}
withAvatar={false}
withFooter={false}
bodyLines={4}
/>
<!-- Square image ratio -->
<SkeletonCard imageRatio="square" />
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
withImage |
boolean |
true |
Show image placeholder |
imageRatio |
"video" | "square" | "portrait" |
"video" |
Image aspect ratio |
withAvatar |
boolean |
true |
Show avatar in header |
bodyLines |
number |
3 |
Number of text lines in body |
withFooter |
boolean |
true |
Show footer section |
animation |
"pulse" | "shimmer" | "wave" | "none" |
"pulse" |
Animation type |
SkeletonTable
Table placeholder with configurable rows and columns:
<!-- Default: 5 rows, 4 columns -->
<SkeletonTable />
<!-- Custom dimensions -->
<SkeletonTable rows={3} columns={6} />
<!-- Without header -->
<SkeletonTable withHeader={false} rows={4} columns={3} />
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
rows |
number |
5 |
Number of table rows |
columns |
number |
4 |
Number of table columns |
withHeader |
boolean |
true |
Show header row |
animation |
"pulse" | "shimmer" | "wave" | "none" |
"pulse" |
Animation type |
Base Skeleton Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant |
"default" | "primary" | "secondary" | "accent" |
"default" |
Visual style |
animation |
"pulse" | "shimmer" | "wave" | "none" |
"pulse" |
Animation type |
shape |
"default" | "circle" | "square" | "pill" |
"default" |
Border radius style |
size |
"xs" | "sm" | "md" | "lg" | "xl" | "full" |
"md" |
Preset height |
width |
string |
undefined |
Custom width ("full", "half", "200px", etc.) |
height |
string |
undefined |
Custom height (overrides size) |
count |
number |
1 |
Number of skeletons to render |
gap |
string |
"0.5rem" |
Gap between multiple skeletons |
delay |
number |
0 |
Animation delay in ms (stagger effect) |
visible |
boolean |
true |
Whether to show the skeleton |
class |
string |
undefined |
Additional CSS classes |
Real-world Examples
Loading State Pattern
<script>
let isLoading = $state(true);
let data = $state(null);
async function fetchData() {
isLoading = true;
data = await api.getData();
isLoading = false;
}
</script>
{#if isLoading}
<SkeletonCard />
{:else}
<Card title={data.title}>
{data.content}
</Card>
{/if}
Social Post Skeleton
<div class="space-y-4">
<SkeletonAvatar withText size="md" />
<SkeletonText lines={2} />
<Skeleton class="aspect-video w-full" />
<div class="flex gap-4">
<Skeleton shape="pill" size="sm" width="60px" />
<Skeleton shape="pill" size="sm" width="60px" />
<Skeleton shape="pill" size="sm" width="60px" />
</div>
</div>
Product List Skeleton
{#each Array(3) as _, i}
<div class="flex gap-4">
<Skeleton shape="default" class="h-16 w-16 shrink-0" />
<div class="flex-1 space-y-2">
<Skeleton size="sm" width="70%" delay={i * 50} />
<Skeleton size="xs" width="50%" delay={i * 50 + 25} />
<Skeleton size="sm" width="30%" delay={i * 50 + 50} />
</div>
</div>
{/each}
Accessibility
- Skeletons use
aria-hidden="true"implicitly as decorative elements - Consider adding screen reader announcements for loading states
- Use the
visibleprop to properly manage loading transitions
Customization
Custom Styles
<Skeleton
class="bg-gradient-to-r from-blue-200 to-purple-200"
size="lg"
width="full"
/>
Custom Animation via CSS
Add custom keyframes to your CSS:
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
@keyframes wave {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}