Components
Skeleton
Skeleton
One composable brick - size it with classes, pick a shape, pick a motion. Compose any loading state instead of picking from prebuilt layouts.
Compose any loading state
One brick, sized with classes - compose the layout you're loading instead of picking from prebuilt shapes. skeleton_group is the accessibility wrapper: role="status" and aria-busy announce loading once (set the label) while the bricks stay aria-hidden, and its animation cascades to everything inside.
<.skeleton_group label="Loading article" class="flex w-full max-w-md flex-col gap-5">
<.skeleton class="h-40 w-full" />
<div class="flex items-center gap-4">
<.skeleton variant="circle" class="size-12 shrink-0" />
<div class="flex-1 space-y-2.5">
<.skeleton variant="text" class="w-1/2" />
<.skeleton variant="text" class="w-3/4" />
</div>
</div>
<.skeleton_text lines={3} />
</.skeleton_group>
Shapes
Three forms cover everything: the block (follows the radius token), variant="circle" for avatars, and skeleton_text for line runs - its widths are deterministic, so LiveView re-renders never make it dance. animation is pulse, shimmer or none; shimmer respects prefers-reduced-motion.
block
circle, shimmer
skeleton_text
<div class="flex items-end justify-center gap-8">
<div class="flex flex-col items-center gap-2">
<.skeleton class="h-16 w-24" />
<span class="text-[11px] text-gray-400">block</span>
</div>
<div class="flex flex-col items-center gap-2">
<.skeleton variant="circle" class="size-16" animation="shimmer" />
<span class="text-[11px] text-gray-400">circle, shimmer</span>
</div>
<div class="flex w-40 flex-col items-center gap-2">
<.skeleton_text lines={3} />
<span class="text-[11px] text-gray-400">skeleton_text</span>
</div>
</div>
Properties
<.skeleton>
| Attribute | Type | Default | Description |
|---|---|---|---|
animation
|
string |
nil
|
pulse (default), shimmer (a moving highlight sweep), or none. When unset, inherits from an enclosing skeleton_group
one of: nil, "pulse", "shimmer", "none"
|
class
|
any |
nil
|
size it here, e.g. class="h-4 w-48" |
kind
|
atom |
nil
|
LEGACY prebuilt layouts - prefer composing skeleton/skeleton_text/skeleton_group
one of: nil, :default, :image, :video, :text, :card, :widget, :list, :testimonial
|
rest
|
global |
||
variant
|
string |
"block"
|
the brick shape: block (theme-radius rectangle), text (a rounded line with a sensible default height), circle (avatar)
one of: "block", "text", "circle"
|
<.skeleton_group>
| Attribute | Type | Default | Description |
|---|---|---|---|
animation
|
string |
nil
|
cascades to every skeleton inside; a brick's own animation attr wins
one of: nil, "pulse", "shimmer", "none"
|
class
|
any |
nil
|
|
label
|
string |
"Loading..."
|
what screen readers announce while the skeleton shows |
rest
|
global |
||
:inner_block
slot
|
slot |
<.skeleton_text>
| Attribute | Type | Default | Description |
|---|---|---|---|
animation
|
string |
nil
|
see skeleton/1
one of: nil, "pulse", "shimmer", "none"
|
class
|
any |
nil
|
|
lines
|
integer |
3
|
number of text lines |
rest
|
global |