{
  "componentId": "skeleton",
  "componentName": "Skeleton",
  "lastReviewed": "2026-05-03",
  "keyboardWalk": [
    {
      "keys": "Tab (skeleton container)",
      "expected": "Skeleton shapes are not in the tab order — they are decorative placeholders. Tab moves through any genuinely focusable elements outside the skeleton; if the eventual content has interactive children, those become focusable only when the load resolves and the skeleton is removed."
    },
    {
      "keys": "Tab (after loading completes)",
      "expected": "Focus moves into the now-rendered content following the eventual content's tab-order rules. The skeleton's removal does not steal focus; if focus was inside the skeleton container at completion (rare), it moves to the first focusable child of the rendered content."
    }
  ],
  "announcements": [
    {
      "trigger": "Skeleton mounts (loading begins)",
      "expected": "SR announces the textual loading-announcement via the role=status sibling region — \"Loading messages\", \"Loading product list\". The shapes themselves are silent (aria-hidden)."
    },
    {
      "trigger": "Loading completes (skeleton replaced by content)",
      "expected": "SR may announce a completion message via the same role=status region updating to \"Messages loaded\" or equivalent, OR by SR re-encountering the now-rendered content. Avoid double-announcing — pick one."
    },
    {
      "trigger": "Loading fails",
      "expected": "SR announces an error via the role=status region or by replacing the skeleton with an error surface that carries its own announcement. Skeleton stuck in permanent loading without an error path is a bug."
    }
  ],
  "axeRules": [
    "aria-allowed-attr",
    "aria-required-attr",
    "aria-hidden-focus",
    "color-contrast",
    "landmark-unique"
  ],
  "_about": "Per-component a11y-acceptance data shaped for direct ingestion into Playwright + @axe-core/playwright or Jest + jest-axe.\n\nSuggested wiring:\n- axeRules → pass to AxeBuilder.options({ runOnly: { type: \"rule\", values: axeRules } }) so the run targets only the rules the canon has pinned for this component (other rules can run in your global pass).\n- keyboardWalk → iterate the entries; each `keys` is a human-readable sequence (e.g. \"Tab → Tab → Esc\"). Translate to page.keyboard.press calls and assert `expected` against the result (focused element, aria-state, visible text, etc.).\n- announcements → assert text content of any aria-live region or capture the accessibility tree at the trigger moment and match against `expected`.\n\nEmpty sub-arrays mean the canon does not yet pin behaviour for that axis on this component, not that none is required."
}