{
 "id": "skeleton-card",
 "name": "Card skeleton",
 "purpose": "A grid of cards that holds each photo, title, detail and price in place while it loads, so the grid never jumps when they arrive.",
 "use_when": [
  "for a grid of cards with photos (stays, products, posts) that loads after the page",
  "a grid of cards loads after the page frame and the count is known or capped"
 ],
 "avoid_when": [
  "one card or a short wait: show nothing or a spinner",
  "an unknown number of items: show one row of bones, not a full page"
 ],
 "type": "states",
 "style": "quiet",
 "width": "wide",
 "tags": {
  "audience": "work",
  "industries": [
   "any"
  ],
  "keywords": [
   "skeleton",
   "loading",
   "cards",
   "grid",
   "no layout shift"
  ]
 },
 "root": ".odl-skeleton-card",
 "parts": [
  {
   "name": "items",
   "selector": ".odl-skeleton-card [data-od-list=\"items\"]",
   "what": "the items, one item each",
   "slot": "list"
  },
  {
   "name": "items.photo",
   "selector": ".odl-skeleton-card [data-od-list=\"items\"] [data-od-media=\"photo\"]",
   "what": "the picture",
   "slot": "media"
  },
  {
   "name": "items.name",
   "selector": ".odl-skeleton-card [data-od-list=\"items\"] [data-od-slot=\"name\"]",
   "what": "the name, as in “Linen room, Old Town”",
   "slot": "text"
  },
  {
   "name": "items.detail",
   "selector": ".odl-skeleton-card [data-od-list=\"items\"] [data-od-slot=\"detail\"]",
   "what": "a short line of detail under the title, as in “2 guests · 1 bed”",
   "slot": "text"
  },
  {
   "name": "items.price",
   "selector": ".odl-skeleton-card [data-od-list=\"items\"] [data-od-slot=\"price\"]",
   "what": "the price, as in “$148 night”",
   "slot": "text"
  }
 ],
 "data": {
  "type": "object",
  "properties": {
   "items": {
    "type": "array",
    "minItems": 1,
    "maxItems": 6,
    "items": {
     "type": "object",
     "properties": {
      "photo": {
       "type": "object",
       "required": [
        "src",
        "alt"
       ],
       "properties": {
        "src": {
         "type": "string",
         "format": "uri",
         "maxLength": 2048
        },
        "alt": {
         "type": "string",
         "maxLength": 120
        }
       },
       "part": "items.photo"
      },
      "name": {
       "type": "string",
       "maxLength": 32,
       "part": "items.name"
      },
      "detail": {
       "type": "string",
       "maxLength": 24,
       "part": "items.detail"
      },
      "price": {
       "type": "string",
       "maxLength": 24,
       "part": "items.price"
      }
     },
     "required": [
      "photo",
      "name",
      "detail",
      "price"
     ]
    },
    "part": "items"
   }
  },
  "required": [
   "items"
  ]
 },
 "states": {
  "shown": [
   "loading",
   "loaded",
   "reduced motion"
  ],
  "edge": {
   "empty": {
    "status": "handled",
    "evidence": "data-od-list",
    "how": "the Smart UI renderer puts one plain 'Nothing to show yet.' line in an empty list"
   },
   "loading": {
    "status": "shown",
    "how": "aria-busy turns each text run, picture and control into a bone of its own size; nothing moves when data arrives.",
    "evidence": "aria-busy=\"true\""
   },
   "error": {
    "status": "not_built",
    "how": "When the request fails, keep the frame and show an error banner with a retry (page-banners) in place of the rows."
   },
   "disabled": {
    "status": "shown",
    "how": "While loading, the content is inert, so placeholders can't be focused or clicked.",
    "evidence": "inert"
   },
   "overflow": {
    "status": "handled",
    "how": "Cards fill as many columns as fit, at least 13rem each; names end in an ellipsis.",
    "evidence": "auto-fit"
   },
   "long_text": {
    "status": "handled",
    "how": "Each line is clamped to one line with an ellipsis, so the skeleton and the loaded layout keep one height.",
    "evidence": "text-overflow: ellipsis"
   },
   "missing_image": {
    "status": "handled",
    "evidence": "data-od-media",
    "how": "the Smart UI renderer swaps a photo that fails to load for a block of the same size with its alt text"
   }
  }
 },
 "tokens": [
  "--ds-border-width-default",
  "--ds-color-bg-action-primary",
  "--ds-color-bg-action-primary-hover",
  "--ds-color-bg-action-primary-pressed",
  "--ds-color-bg-neutral-subtle",
  "--ds-color-border-default",
  "--ds-color-border-focus",
  "--ds-color-surface-raised",
  "--ds-color-surface-sunken",
  "--ds-color-text-link",
  "--ds-color-text-on-action",
  "--ds-color-text-primary",
  "--ds-color-text-secondary",
  "--ds-focus-ring-offset",
  "--ds-focus-ring-width",
  "--ds-font-weight-heading",
  "--ds-font-weight-label",
  "--ds-icon-stroke-md",
  "--ds-motion-duration-extra",
  "--ds-motion-duration-short",
  "--ds-motion-easing-linear",
  "--ds-motion-easing-standard",
  "--ds-opacity-disabled-container",
  "--ds-radius-container",
  "--ds-radius-control",
  "--ds-radius-detail",
  "--ds-size-control-md",
  "--ds-size-control-sm",
  "--ds-size-icon-sm",
  "--ds-size-target-min",
  "--ds-space-inline-md",
  "--ds-space-inline-sm",
  "--ds-space-inset-md",
  "--ds-space-inset-sm",
  "--ds-space-stack-lg",
  "--ds-space-stack-md",
  "--ds-space-stack-xs",
  "--ds-state-skeleton-base",
  "--ds-state-skeleton-highlight",
  "--ds-state-skeleton-pulse",
  "--ds-state-skeleton-radius",
  "--ds-text-body-md",
  "--ds-text-body-sm",
  "--ds-text-label-lg",
  "--ds-text-title-md",
  "--ds-text-title-sm"
 ],
 "a11y": {
  "summary": "While loading, the content area is aria-busy and inert, so nobody can tab into placeholders; a polite status says when loading starts and when it is done. Under reduced motion the bones stay still.",
  "roles": [
   "list",
   "region",
   "status"
  ],
  "keyboard": [
   "Tab reaches Show loaded (the demo toggle), then the loaded content once it's ready; nothing inside is focusable while loading"
  ],
  "controls": {
   "button": 1,
   "input": 0,
   "link": 0,
   "select": 0,
   "textarea": 0,
   "summary": 0
  },
  "focus_visible": true,
  "reduced_motion": true,
  "min_target": true
 },
 "files": {
  "html": "components/skeleton-card/component.html",
  "bytes": 8589,
  "script": true
 },
 "source": null,
 "photos": 3,
 "urls": {
  "html": "https://opendesigner.dev/library/skeleton-card.html",
  "json": "https://opendesigner.dev/library/skeleton-card.json",
  "page": "https://opendesigner.dev/library/skeleton-card/"
 },
 "get": {
  "curl": "curl -fsSLo skeleton-card.html https://opendesigner.dev/library/skeleton-card.html",
  "opendesigner": "python3 path/to/skills/opendesigner/scripts/library.py add skeleton-card --from https://opendesigner.dev",
  "prompt": "Add the OpenDesigner component skeleton-card to this project: https://opendesigner.dev/library/skeleton-card.html. Keep its markup and states; map its --ds-* variables to my tokens."
 }
}
