{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "skeleton",
  "title": "Skeleton",
  "description": "A grey stand-in for content on its way, with a fixed size so nothing moves when the real content arrives; pulses slowly and stands still under reduced motion. Also SkeletonText for lines of text.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/skeleton/skeleton.tsx",
      "content": "// A grey stand-in for content that has not arrived yet. It has a fixed size, so the page does not move when the real\n// content replaces it: give it the width and height of what it stands for. It pulses slowly; under reduced motion it\n// stands still as a flat grey block. The colour is the theme's `muted`, so it follows light and dark by itself.\n// Screen readers skip it (`aria-hidden`): the region around it says that something is loading (see loading-region).\nimport type { ComponentPropsWithoutRef, CSSProperties } from 'react'\nimport { cn } from '@/lib/utils'\n\ntype Size = number | string\n\nconst px = (v: Size | undefined) => (typeof v === 'number' ? `${v}px` : v)\n\nexport type SkeletonProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {\n  /** Width of the block. A number is pixels; a string is any CSS length (\"100%\", \"12rem\"). Default \"100%\". */\n  width?: Size\n  /** Height of the block, the same units. Default 16. */\n  height?: Size\n  /** A circle of the given `width` (and the same height), for avatars. */\n  circle?: boolean\n  /** Classes for the block. */\n  className?: string\n}\n\nexport function Skeleton({ width = '100%', height = 16, circle = false, className, ...rest }: SkeletonProps) {\n  const style: CSSProperties = { width: px(width), height: px(circle ? width : height) }\n  return (\n    <div\n      aria-hidden=\"true\"\n      {...rest}\n      style={style}\n      className={cn('max-w-full shrink-0 bg-muted motion-safe:animate-pulse', circle ? 'rounded-full' : 'rounded-md', className)}\n    />\n  )\n}\n\nexport type SkeletonTextProps = Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'> & {\n  /** How many lines. Default 3. */\n  lines?: number\n  /** Height of each line in pixels. Default 14. */\n  lineHeight?: number\n  /** Space between the lines in pixels. Default 8. */\n  gap?: number\n  /** Width of the last line, as a share of the box. Default \"60%\". */\n  lastLineWidth?: Size\n  /** Classes for the box. */\n  className?: string\n}\n\n/** Lines of text, the last one shorter. Its height is lines x lineHeight + gaps, so it can match a paragraph. */\nexport function SkeletonText({ lines = 3, lineHeight = 14, gap = 8, lastLineWidth = '60%', className, ...rest }: SkeletonTextProps) {\n  const count = Math.max(1, Math.floor(lines))\n  return (\n    <div aria-hidden=\"true\" {...rest} className={cn('flex w-full flex-col', className)} style={{ gap }}>\n      {Array.from({ length: count }, (_, i) => (\n        <Skeleton key={i} height={lineHeight} width={i === count - 1 && count > 1 ? lastLineWidth : '100%'} />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "meta": {
    "tier": "free",
    "added": "2026-11-02",
    "usedIn": [],
    "props": [
      {
        "name": "width",
        "type": "Size",
        "default": "'100%'",
        "required": false,
        "description": "Width of the block. A number is pixels; a string is any CSS length (\"100%\", \"12rem\"). Default \"100%\"."
      },
      {
        "name": "height",
        "type": "Size",
        "default": "16",
        "required": false,
        "description": "Height of the block, the same units. Default 16."
      },
      {
        "name": "circle",
        "type": "boolean",
        "default": "false",
        "required": false,
        "description": "A circle of the given `width` (and the same height), for avatars."
      },
      {
        "name": "className",
        "type": "string",
        "default": null,
        "required": false,
        "description": "Classes for the block."
      }
    ],
    "extends": "Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'style'>",
    "a11y": [
      {
        "topic": "screen-reader",
        "text": "Every block is aria-hidden, so a screen reader hears nothing from it. The region around it (loading-region) says that something is loading; used alone, give the container aria-busy and a role=\"status\" line."
      },
      {
        "topic": "motion",
        "text": "The slow pulse is motion-safe only: under reduced motion the block is a flat grey, with no shimmer and no movement. Sizes are fixed, so the swap to real content moves nothing."
      }
    ]
  },
  "categories": [
    "surfaces"
  ],
  "type": "registry:component"
}