{
  "componentId": "code-block",
  "componentName": "Code Block",
  "lastReviewed": "2026-05-04",
  "keyboardWalk": [
    {
      "keys": "Tab",
      "expected": "For block variant with copy-button, Tab moves focus to the copy-button; Tab again moves focus past the code-block. For block variant without copy-button but with horizontal-scroll region, Tab moves into the focusable scrollable-region; arrow keys scroll. For inline variant, the code text is non- focusable and Tab skips over it. Syntax- highlight tokens never receive focus."
    },
    {
      "keys": "Enter / Space (focus on copy-button)",
      "expected": "Activates the copy. Clipboard write begins; `aria-busy=\"true\"` on the button briefly (or the visible icon transitions to a loading state); on resolve, the visible icon transitions to a checkmark and the live-region announces \"Copied to clipboard\". On rejection, the icon transitions to error and the live-region announces \"Copy failed\"."
    },
    {
      "keys": "Arrow keys (focus on scrollable-region of overflowing block)",
      "expected": "Scrolls the overflow horizontally / vertically. Standard browser behaviour for focusable scroll-containers. The region's `tabindex=\"0\"` plus implicit keyboard-scroll behaviour drives this."
    }
  ],
  "announcements": [
    {
      "trigger": "SR encounters a block variant in reading order",
      "expected": "SR announces \"preformatted text\" or \"code block\" (the `<pre>` semantic). The code text follows in reading order; syntax- highlight wrappers are skipped per their `aria-hidden`. After the code, the copy-button announces as \"Copy code, button\" and the live-region (initially empty) announces nothing until activated."
    },
    {
      "trigger": "SR encounters an inline variant in flowing prose",
      "expected": "SR announces \"code\" entering the inline code semantic and \"code\" or silence leaving it (varies by SR / browser). The text content reads inline with the surrounding prose; users hear the code-vs-prose boundary."
    },
    {
      "trigger": "User activates the copy-button (success)",
      "expected": "Live-region updates to \"Copied to clipboard\" and SR announces it via `aria-live=\"polite\"`. The button's accessible name stays \"Copy code\" (does not change) — the live-region carries the transient confirmation. After ~3 seconds, the live-region clears so the next copy is announced fresh."
    },
    {
      "trigger": "User activates the copy-button (failure)",
      "expected": "Live-region updates to \"Copy failed\" and SR announces it. The button stays in its canonical state; no visual lock-out. Common failure modes: insecure context (HTTP page), permission policy, browser rejection of large clipboard payloads."
    }
  ],
  "axeRules": [
    "aria-allowed-attr",
    "aria-required-attr",
    "aria-valid-attr-value",
    "button-name",
    "color-contrast"
  ],
  "_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."
}