component — feedback

Skeleton

A pulsing placeholder for content that is still loading, styled with the quebi design system. Compose several to mirror the shape of the eventual content.

  • loading
  • placeholder
  • shimmer
  • pulse
  • feedback

07 examples

Default

A single block placeholder. Set width and height with utility classes.

Soft

A fainter fill for nested or secondary placeholders.

Text lines

Stack a few skeletons of varying width to suggest a paragraph.

Avatar with lines

Mix a circle with text lines to mirror a list item or comment.

Repeated unit

A list's fallback is its unit, repeated — heading, description, box — so the stack is roughly the height the content will be and nothing below it jumps when the content lands. One bar for a whole list tells the reader nothing about how much is coming.

Code block

Lines of varying width and indentation, on the surface the code will land on. Widths come from a fixed list by index, never Math.random(): a prerendered page would otherwise bake one set of widths into its HTML and roll different ones at hydration.

Card

A full card placeholder built from several skeletons.

source

skeleton.tsx

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

import { cn } from "@/lib/utils"

/**
 * Skeleton — quebi design system
 *
 * A pulsing placeholder for content that is still loading. Compose several
 * skeletons to mirror the shape of the eventual content.
 *
 * The fill is `raised` — the ground of a hovered row, an ink tint in both
 * themes — and the bar is square, like the content it stands in for. `soft`
 * drops it further for nested or secondary placeholders.
 *
 * A Skeleton *is* the component — there is no text, border or glyph carrying
 * the shape if the fill fails — which is why the pulse is `quebi-pulse` rather
 * than Tailwind's `animate-pulse`: that one troughs at 0.5 opacity, halving a
 * fill this faint every two seconds.
 */
export interface SkeletonProps extends React.ComponentProps<"div"> {
  /** Use a fainter fill for nested or secondary placeholders. */
  soft?: boolean
}

export function Skeleton({ ref, soft = false, className, ...props }: SkeletonProps) {
  return (
    <div
      data-slot="skeleton"
      ref={ref}
      className={cn(
        "shrink-0 quebi-pulse",
        soft ? "bg-quebi-raised/60" : "bg-quebi-raised",
        className,
      )}
      {...props}
    />
  )
}