Skip to main content

Skeleton

Show a placeholder while content is loading.

Use Skeleton to reserve space for content while it loads. Match the placeholder’s dimensions and shape to the content it will replace.

Basic usage#

import { Skeleton } from 'moraine'

export function Example() {
  return (
    <div class="flex gap-4 max-w-sm w-full items-center">
      <Skeleton class="rounded-full shrink-0 size-12" />
      <div class="flex-1 min-w-0 space-y-2">
        <Skeleton class="h-4 max-w-[250px] w-full" />
        <Skeleton class="h-4 max-w-[200px] w-full" />
      </div>
    </div>
  )
}

Playground#

Props
Slots

Anatomy#

Skeleton [component; slot=root; <div>]

Usage#

Size and appearance#

Skeleton renders one <div> with a muted background, medium corners, and a pulse animation. Set its dimensions with class or an object style, and use utilities such as rounded-full to change its shape. Native div attributes and children are forwarded to the root.

Animation variants#

The default variant="pulse" fades the placeholder in and out. Use variant="shimmer" for a linear gradient sweep across the placeholder. Both animations respect the user’s reduced-motion preference.

Shimmer uses Moraine’s loop duration, which defaults to two seconds. Set --mo-anim-duration-loop or --mo-anim-duration in style to change its speed. Configure skeleton.defaultVariants.variant in your Theme to set the default animation across your application.

import { Skeleton } from 'moraine'

export function AnimationVariants() {
  return (
    <div class="gap-6 grid max-w-sm w-full">
      <div class="space-y-2">
        <p class="text-sm text-muted-foreground">Pulse</p>
        <Skeleton class="h-4 w-full" />
        <Skeleton class="h-4 w-3/4" />
      </div>
      <div class="space-y-2">
        <p class="text-sm text-muted-foreground">Shimmer</p>
        <Skeleton variant="shimmer" class="h-4 w-full" />
        <Skeleton variant="shimmer" class="h-4 w-3/4" />
      </div>
    </div>
  )
}

Examples#

Avatar#

import { Skeleton } from 'moraine'

export function SkeletonAvatar() {
  return (
    <div class="flex gap-4 w-fit items-center">
      <Skeleton class="rounded-full shrink-0 size-10" />
      <div class="gap-2 grid">
        <Skeleton class="h-4 w-[150px]" />
        <Skeleton class="h-4 w-[100px]" />
      </div>
    </div>
  )
}

Card#

import { Card, Skeleton } from 'moraine'

export function SkeletonCard() {
  return (
    <Card class="max-w-xs w-full">
      <Card.Header>
        <div class="flex flex-col gap-2">
          <Skeleton class="h-4 w-2/3" />
          <Skeleton class="h-4 w-1/2" />
        </div>
      </Card.Header>
      <Card.Body>
        <Skeleton class="w-full aspect-video" />
      </Card.Body>
    </Card>
  )
}

Text#

import { Skeleton } from 'moraine'

export function SkeletonText() {
  return (
    <div class="flex flex-col gap-2 max-w-xs w-full">
      <Skeleton class="h-4 w-full" />
      <Skeleton class="h-4 w-full" />
      <Skeleton class="h-4 w-3/4" />
    </div>
  )
}

Form#

import { Skeleton } from 'moraine'

export function SkeletonForm() {
  return (
    <div class="flex flex-col gap-7 max-w-xs w-full">
      <div class="flex flex-col gap-3">
        <Skeleton class="h-4 w-20" />
        <Skeleton class="h-8 w-full" />
      </div>
      <div class="flex flex-col gap-3">
        <Skeleton class="h-4 w-24" />
        <Skeleton class="h-8 w-full" />
      </div>
      <Skeleton class="h-8 w-24" />
    </div>
  )
}

Table#

import { Skeleton } from 'moraine'
import { For } from 'solid-js'

export function SkeletonTable() {
  return (
    <div class="flex flex-col gap-2 max-w-sm w-full">
      <For each={Array.from({ length: 5 })}>
        {() => (
          <div class="flex gap-4">
            <Skeleton class="flex-1 h-4" />
            <Skeleton class="h-4 w-24" />
            <Skeleton class="h-4 w-20" />
          </div>
        )}
      </For>
    </div>
  )
}

Props#

Renders a <div> element by default.

Prop