{
  "name": "Skeleton",
  "source": "src/components/Skeleton.tsx",
  "propsType": "SkeletonProps",
  "description": "Skeleton — the shape of content that has not arrived.\n\nIT IS HIDDEN FROM ASSISTIVE TECHNOLOGY, ALWAYS, AND THAT IS THE WHOLE POINT\n\nA skeleton is a picture of content, and a screen reader cannot use a picture\nof content. Announcing it produces a stream of empty boxes between the user\nand the thing they asked for. So every skeleton is `aria-hidden`, with no way\nto opt out — a prop for that would only ever be used by mistake.\n\nWHICH MEANS THE CALLER OWES THE ANNOUNCEMENT, AND THIS COMPONENT CANNOT DO IT\n\nHiding the placeholder is only half an answer: something still has to tell a\nscreen-reader user that the region is loading. That belongs on the region\nbeing replaced — `aria-busy=\"true\"` while it loads — because only the caller\nknows where that region starts and ends. `Spinner` is the other half when the\nwait deserves an announcement of its own.\n\n  <section aria-busy={isLoading}>\n    {isLoading ? <Skeleton lines={3} /> : <Rows data={data} />}\n  </section>\n\nThis is the one component in the system whose correct use REQUIRES something\nof the caller that the type system cannot check, which is why it is stated\nhere, in the contract, and in the story.",
  "import": "import { Skeleton } from 'ionbase-ui';",
  "status": "stable",
  "summary": "The shape of content that has not arrived. Always hidden from assistive technology — the caller owns the announcement.",
  "useWhen": [
    "a region will be replaced by content whose shape is known in advance",
    "the layout would otherwise jump when the content lands",
    "several regions load independently and a single page spinner would hide that"
  ],
  "useInstead": [
    {
      "when": "the shape of what is coming is not known",
      "use": "Spinner",
      "why": "a skeleton that does not match what replaces it is a worse promise than no promise"
    },
    {
      "when": "the region is empty because there is nothing to show",
      "use": "EmptyState",
      "why": "a skeleton that never resolves is how an empty list looks like a broken one"
    }
  ],
  "variants": {
    "variant": {
      "text": {
        "use": "lines of copy",
        "note": "height comes from the line box, so the block occupies what the real paragraph will. With `lines`, the last is drawn short because real paragraphs end mid-measure"
      },
      "circle": {
        "use": "an avatar or an icon slot"
      },
      "rect": {
        "use": "a card, a thumbnail, a chart"
      }
    }
  },
  "a11y": {
    "requires": [
      "`aria-busy=\"true\"` on the region being replaced, set by the CALLER — this component cannot do it, because only the caller knows where the region starts and ends"
    ],
    "notes": [
      "every skeleton is aria-hidden with no opt-out: a screen reader cannot use a picture of content, and announcing it puts a stream of empty boxes between the user and what they asked for",
      "prefers-reduced-motion stops the pulse outright, unlike Spinner and ProgressBar — a skeleton conveys nothing a static block does not, so there is no signal to preserve"
    ]
  },
  "antiPatterns": [
    {
      "dont": "<Skeleton /> inside a region with no aria-busy",
      "do": "<section aria-busy={isLoading}>{isLoading ? <Skeleton lines={3} /> : <Rows />}</section>",
      "why": "the skeleton is hidden, so without aria-busy a screen-reader user is told nothing at all — the region simply reads as empty"
    },
    {
      "dont": "a skeleton whose shape does not match the content that replaces it",
      "do": "match the line count and block sizes to the real thing",
      "why": "the entire benefit is that the layout does not move; a mismatched skeleton pays the cost and keeps the jump"
    },
    {
      "dont": "leaving skeletons on screen when a request fails",
      "do": "render EmptyState with reason=\"error\"",
      "why": "a skeleton that never resolves is indistinguishable from a hung page"
    }
  ],
  "stylesheet": "src/styles/skeleton.css",
  "tokens": [
    "--border-width-default",
    "--icon-size-lg",
    "--ion-duration-slow",
    "--ion-ease-in-out",
    "--ion-skeleton-height",
    "--ion-skeleton-radius",
    "--ion-skeleton-width",
    "--radius-full",
    "--radius-md",
    "--radius-sm",
    "--spacing-16",
    "--spacing-64",
    "--spacing-8",
    "--surface-muted",
    "--type-body-line-height"
  ],
  "props": {
    "variant": {
      "type": "SkeletonVariant | undefined",
      "required": false,
      "origin": "own",
      "values": [
        "circle",
        "text",
        "rect"
      ]
    },
    "width": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Any CSS length. Defaults to filling the width available."
    },
    "height": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Any CSS length. `text` derives its height from the line box instead."
    },
    "lines": {
      "type": "number | undefined",
      "required": false,
      "origin": "own",
      "description": "`text` only: how many lines to draw. The last is drawn short."
    }
  },
  "propCounts": {
    "own": 4,
    "aria": 0,
    "dom": 277,
    "other": 0
  }
}
