|Design System

AI Component

Skeleton

Animated placeholder shapes that preview content layout while data loads or AI generates a response.

Preview

Source

Full component implementation using the design system tokens.

tsx
const shimmerClass = "animate-pulse bg-overlay/5 rounded";

export function DSSkeleton({
  variant = "text",
  lines = 3,
}: {
  variant?: "text" | "circle" | "card" | "chat";
  lines?: number;
}) {
  if (variant === "circle") {
    return <div className={`w-10 h-10 rounded-full ${shimmerClass}`} />;
  }

  if (variant === "card") {
    return (
      <div className="border border-overlay/10 rounded-xl bg-surface p-5 w-full max-w-xs">
        <div className={`h-4 w-24 mb-3 ${shimmerClass}`} />
        <div className={`h-3 w-full mb-2 ${shimmerClass}`} />
        <div className={`h-3 w-3/4 mb-4 ${shimmerClass}`} />
        <div className={`h-8 w-20 rounded-lg ${shimmerClass}`} />
      </div>
    );
  }

  if (variant === "chat") {
    return (
      <div className="flex gap-3 w-full">
        <div className={`w-8 h-8 rounded-full shrink-0 ${shimmerClass}`} />
        <div className="flex-1 max-w-xs">
          <div className="rounded-xl rounded-tl-sm p-4 bg-surface">
            <div className={`h-3 w-full mb-2 ${shimmerClass}`} />
            <div className={`h-3 w-4/5 mb-2 ${shimmerClass}`} />
            <div className={`h-3 w-2/3 ${shimmerClass}`} />
          </div>
          <div className={`h-2 w-16 mt-1.5 ${shimmerClass}`} />
        </div>
      </div>
    );
  }

  return (
    <div className="space-y-2 w-full max-w-xs">
      {Array.from({ length: lines }).map((_, i) => (
        <div
          key={i}
          className={`h-3 ${shimmerClass}`}
          style={{ width: `${100 - i * 15}%` }}
        />
      ))}
    </div>
  );
}

Props

All available props with types and defaults.

PropTypeDescription
variant'text' | 'circle' | 'card' | 'chat'Shape preset matching the content being loaded
linesnumberNumber of text lines (text variant only)

Variants

Text

Multi-line text placeholder with decreasing widths.

tsx
<DSSkeleton variant="text" lines={3} />

Circle

Avatar placeholder for user/model icons.

tsx
<DSSkeleton variant="circle" />

Card

Full card skeleton with title, body, and action area.

tsx
<DSSkeleton variant="card" />

Chat

Chat bubble skeleton with avatar — shows before AI response arrives.

tsx
<DSSkeleton variant="chat" />

Prompt Guide

Prompt Guide — Skeleton

Use for

  • Initial page load before content arrives
  • Before AI response begins streaming
  • Lazy-loaded panels and sidebars
  • Placeholder for artifacts being generated

Don't use for

  • After content has started streaming — switch to the Chat Bubble streaming state
  • Error states — show an error message, not a skeleton
  • Empty states — skeletons imply content is coming; if nothing will load, show an empty state message

AI Context

Skeletons bridge the gap between user action and AI response. When a user sends a message, immediately show a chat skeleton — it signals the system is working before any tokens arrive. Once the first token streams in, replace the skeleton with a streaming Chat Bubble. The card skeleton works for artifact generation: show it while the AI builds a code block or chart, then swap in the real Artifact Card.