{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"skeleton","type":"registry:ui","title":"Skeleton","description":"Gray shapes in the exact size of the content they stand in for – a soft sheen sweeping across all of them in step, a Loading wrapper that tells screen readers and fades in late, so fast loads never flash","dependencies":[],"files":[{"path":"registry/skeleton/skeleton.tsx","type":"registry:ui","content":"\"use client\";\n\nimport type { ComponentPropsWithRef, CSSProperties } from \"react\";\n\n/*\n * Skeleton – a single self-contained file.\n * Skeleton: a gray shape that stands in for text, an avatar or a button while the data loads; a soft sheen sweeps\n * across it, every skeleton in step. Give it the size of what it replaces – with `line` it sits in a box one text line\n * tall, so a row keeps its height when the words land. Loading: wraps the skeletons of one area, tells screen readers\n * it is busy and fades them in a beat late, so a fast load never flashes them.\n * The styles are injected below, so no extra files are needed.\n */\n\ntype Length = number | string;\n\nexport type SkeletonProps = Omit<ComponentPropsWithRef<\"span\">, \"children\"> & {\n  /** px or any CSS length */\n  width?: Length;\n  /** The shape's own height – px or any CSS length */\n  height?: Length;\n  /** Corner radius – px, \"50%\" for a circle */\n  radius?: Length;\n  /**\n   * Sits the shape in a box as tall as the text line it replaces (true = 1lh, or a length), centered in it –\n   * a 10px bar in a 20px line, so the row is exactly as tall as it will be with text\n   */\n  line?: boolean | Length;\n};\n\nexport function Skeleton({ width = \"100%\", height = 12, radius, line, className, style, ...rest }: SkeletonProps) {\n  const bar = (\n    <span\n      {...(line ? {} : rest)}\n      className={[\"ui-skl\", line ? null : className].filter(Boolean).join(\" \")}\n      style={{ width, height, borderRadius: radius, ...(line ? null : style) }}\n      aria-hidden=\"true\"\n    />\n  );\n  return (\n    <>\n      {/* React 19 hoists this into <head> once, no matter how many skeletons render */}\n      <style href=\"ui-skeleton\" precedence=\"default\">\n        {css}\n      </style>\n      {line ? (\n        <span\n          {...rest}\n          className={[\"ui-skl__line\", className].filter(Boolean).join(\" \")}\n          style={{ height: line === true ? \"1lh\" : line, ...style } as CSSProperties}\n          aria-hidden=\"true\"\n        >\n          {bar}\n        </span>\n      ) : (\n        bar\n      )}\n    </>\n  );\n}\n\nexport type LoadingProps = Omit<ComponentPropsWithRef<\"div\">, \"role\"> & {\n  /** What loads, for screen readers – \"Loading runs\" */\n  label?: string;\n};\n\n/**\n * Wraps the skeletons that stand in for one area: role=\"status\" with aria-busy, and a fade-in after 150ms.\n * Give it the real layout's class, so the grid or list is the same one the data fills.\n */\nexport function Loading({ label = \"Loading\", className, children, ...rest }: LoadingProps) {\n  return (\n    <div {...rest} className={[\"ui-skl-loading\", className].filter(Boolean).join(\" \")} role=\"status\" aria-busy=\"true\" aria-label={label}>\n      <style href=\"ui-skeleton\" precedence=\"default\">\n        {css}\n      </style>\n      {children}\n    </div>\n  );\n}\n\nconst css = /* css */ `\n/* Colors and motion as variables – override them on .ui-skl (see Theming in the docs) */\n.ui-skl {\n  --ui-skl-fill: #efefef;\n  --ui-skl-sheen: rgba(255, 255, 255, 0.8);\n  --ui-skl-speed: 1.6s;\n  --ui-skl-ease: var(--aui-ease, cubic-bezier(0.22, 1, 0.36, 1));\n\n  position: relative;\n  display: block;\n  flex: none;\n  box-sizing: border-box;\n  max-width: 100%;\n  overflow: hidden;\n  border-radius: var(--aui-radius-sm, 6px);\n  background: var(--ui-skl-fill);\n}\n/* The sheen: every skeleton sweeps at the same time, so a whole list breathes as one */\n.ui-skl::after {\n  content: \"\";\n  position: absolute;\n  inset: 0;\n  background: linear-gradient(90deg, transparent, var(--ui-skl-sheen), transparent);\n  translate: -100% 0;\n  animation: ui-skl-sweep var(--ui-skl-speed) var(--ui-skl-ease) infinite;\n}\n@keyframes ui-skl-sweep { 60%, 100% { translate: 100% 0; } }\n\n/* line: a box one text line tall, the shape centered in it */\n.ui-skl__line { display: flex; align-items: center; min-width: 0; max-width: 100%; }\n\n/* The wrapper fades in a beat late – a load under 150ms never shows the skeletons at all */\n.ui-skl-loading { --ui-skl-delay: 150ms; animation: ui-skl-in 300ms ease var(--ui-skl-delay) both; }\n@keyframes ui-skl-in { from { opacity: 0; } }\n\n@media (prefers-reduced-motion: reduce) {\n  .ui-skl::after { display: none; }\n}\n\n/* Dark mode: follows a .dark class or data-theme=\"dark\" on any ancestor, e.g. <html> */\n:where(.dark, [data-theme=\"dark\"]) .ui-skl {\n  --ui-skl-fill: #242428;\n  --ui-skl-sheen: rgba(255, 255, 255, 0.05);\n}\n`;\n"}]}