{
  "componentId": "text-input",
  "componentName": "Text Input",
  "lastReviewed": "2026-05-04",
  "keyboardWalk": [
    {
      "keys": "Tab",
      "expected": "Focus enters the input. Subsequent Tab moves to the trailing-icon button (if interactive and visible), then out of the field. Disabled inputs skip from focus order; readonly inputs receive focus normally."
    },
    {
      "keys": "Shift+Tab",
      "expected": "Reverse focus traversal — out of the field via the label / leading icon (which are not focusable), or back into the previous field."
    },
    {
      "keys": "Type any character",
      "expected": "Character appends to the input's value. `valueChange` fires synchronously. For type-specific filtering (`type=\"number\"` rejects letters at the platform level), browser handles the rejection; ARIA-invalid does not flip."
    },
    {
      "keys": "Backspace / Delete",
      "expected": "Removes the character at the caret. With a selection active, deletes the selection. `valueChange` fires. For empty-input-with-required, transition to invalid runs on blur not on backspace."
    },
    {
      "keys": "Enter (in a form)",
      "expected": "Submits the parent `<form>` (browser default). The input itself does not handle Enter — the form's submit handler runs; form validation triggers if not already in invalid state."
    }
  ],
  "announcements": [
    {
      "trigger": "Focus enters the input",
      "expected": "SR announces the label, the input role (\"edit text\" for `type=\"text\"`, \"email\" for `type=\"email\"`), the current value if non-empty, and any `aria-describedby` content (description and / or error message). Required state announces if `required` or `aria-required=\"true\"`."
    },
    {
      "trigger": "Validation transitions to invalid",
      "expected": "Polite live region (`aria-live=\"polite\"` or `role=\"alert\"` on the error node) announces the error message. `aria-invalid=\"true\"` on the input means subsequent re-focus announces \"invalid\" alongside the label and role."
    },
    {
      "trigger": "User types into a previously-invalid field",
      "expected": "Validation re-runs at the validation cadence (typically on blur). On invalid → valid transition, SR re-focus announces normally without the invalid suffix; the error node clears."
    }
  ],
  "axeRules": [
    "aria-input-field-name",
    "aria-required-attr",
    "aria-valid-attr-value",
    "color-contrast",
    "label",
    "label-content-name-mismatch"
  ],
  "_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."
}