{
  "$schema": "https://www.digitaltableteur.com/schemas/contract-spec/1.0/contract.schema.json",
  "specVersion": "1.0",
  "name": "LanguageSwitcher",
  "status": "stable",
  "description": "Compact language selector: a current-language trigger that fans out the other locales as a button group.",
  "package": {
    "name": "@digitaltableteur/react",
    "import": "import { LanguageSwitcher } from \"@digitaltableteur/react\";"
  },
  "props": {
    "languages": {
      "type": "LanguageSwitcherOption[]",
      "optional": false,
      "description": "Available languages: code (also set as lang), visible label and ariaLabel,\nthe language's own name used in the \"<label>, <name>\" accessible name."
    },
    "currentLang": {
      "type": "string",
      "optional": false,
      "description": "Currently selected language code."
    },
    "onLanguageChange": {
      "type": "(code: string) => void",
      "optional": false,
      "description": "Called with the selected language code."
    },
    "className": {
      "type": "string",
      "optional": true,
      "description": "Optional classes on the group wrapper."
    },
    "buttonClassName": {
      "type": "string",
      "optional": true,
      "description": "Base class override applied to every language button."
    },
    "activeButtonClassName": {
      "type": "string",
      "optional": true,
      "description": "Class override for the current-language trigger."
    },
    "openTriggerClassName": {
      "type": "string",
      "optional": true,
      "description": "Class override for the trigger while the tray is open."
    },
    "floatedButtonClassName": {
      "type": "string",
      "optional": true,
      "description": "Class override for the fanned-out tray options."
    }
  },
  "accessibility": {
    "criteria": [
      {
        "id": "axe-no-violations",
        "statement": "Required stories produce no axe violations at the configured severity.",
        "verification": "automated",
        "wcag": [
          "2.5.8"
        ],
        "check": "npm run test:stories:matrix:ci",
        "evidence": [
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:01.794Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:43.577Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.324Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.356Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:44.085Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.843Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.834Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:44.540Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:16.307Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.076Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:43.844Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.573Z",
            "passed": true,
            "runner": "local-recapture"
          }
        ]
      },
      {
        "id": "accessibility-tree",
        "statement": "The accessibility tree (role, name, state) matches the committed snapshot for every required story.",
        "verification": "automated",
        "wcag": [
          "1.3.1",
          "4.1.2"
        ],
        "check": "npm run test:stories:matrix:ci",
        "evidence": [
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:01.794Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:14.770Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:43.577Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.324Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.356Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.322Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:44.085Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.843Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-forced-colors.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.869Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-forced-colors.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.750Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:44.570Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-forced-colors.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:16.344Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.834Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.723Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:44.540Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:16.307Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.dark.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:39:02.076Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.047Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:17:43.844Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.light.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:25:15.573Z",
            "passed": true,
            "runner": "local-recapture"
          }
        ]
      },
      {
        "id": "forced-colors-real-browser",
        "statement": "The component remains legible and operable under a real browser forced-colors: active pass.",
        "verification": "automated",
        "check": "npm run test:stories:hc",
        "evidence": [
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-default.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:14.770Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-example.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.322Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-keyboard-toggle.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.723Z",
            "passed": true,
            "runner": "local-recapture"
          },
          {
            "artifact": "../../../nextjs-app/shared/components/LanguageSwitcher/__a11y-evidence__/navigation-languageswitcher-playground.forced-colors.json",
            "sourceCommit": "930c562b591b6d9967e1e8c214899052624d9a2f",
            "capturedAt": "2026-11-03T22:46:15.047Z",
            "passed": true,
            "runner": "local-recapture"
          }
        ]
      },
      {
        "id": "keyboard-contract",
        "statement": "Every documented keyboard interaction works as specified.",
        "verification": "unverified",
        "wcag": [
          "2.1.1",
          "2.1.2"
        ],
        "note": "No exportable evidence record for this check; re-run the check to capture one."
      },
      {
        "id": "aria-requirements",
        "statement": "Documented ARIA roles, states, and properties are present on the rendered output.",
        "verification": "unverified",
        "wcag": [
          "4.1.2"
        ],
        "note": "No exportable evidence record for this check; re-run the check to capture one."
      },
      {
        "id": "human-a11y-review",
        "statement": "A human completed an end-to-end accessibility review of this component.",
        "verification": "manual",
        "note": "2026-07-05 — beta→stable (fleet #14): the fan animation is reduced-motion-guarded via framer useReducedMotion (spring→plain fade); the button color transition now also carries motion-reduce:transition-none (unit-tested), so a11y.reducedMotion is fully honest. State-conditional className props (openTriggerClassName, floatedButtonClassName) are EFFECT_EXEMPT in audit:controls (only apply while the tray is open) with the KeyboardToggle play + unit tests exercising both states. Re-verified: axe + KeyboardToggle play green in light/dark/forced-colors, 100% controls operable / 0 inert, AT snapshots captured 4 modes — KeyboardToggle drives open→Escape and settles closed (so its snapshot is the closed group), while the FanOpen example snapshots the open fan (all locales + expanded trigger). 2026-06-02 origin: role=group + aria-label; trigger exposes aria-expanded/aria-controls/aria-current; options are native buttons with aria-label; Escape + outside-click close. 2026-10-15: the header passed an action phrase as the language name, so the trigger announced the code twice and an instruction to switch to the active language. ariaLabel is now documented as the language name only; options are named '<label>, <name>' with lang; exact accessible names are unit-tested. Same day, a council review found the trigger name changed with state (Show/Hide) and the trigger carried aria-current; the name is now stable ('EN, English'), aria-expanded carries state, and aria-current is gone.",
        "reviewedAt": "2026-11-04"
      }
    ]
  },
  "governance": {
    "owner": "design-system",
    "lastReviewed": "2026-11-04",
    "note": "Reviewed by Petri Lahdelma; prepared by Claude from a code review on 2026-11-03"
  },
  "source": [
    "../../../nextjs-app/shared/components/LanguageSwitcher/LanguageSwitcher.module.css",
    "../../../nextjs-app/shared/components/LanguageSwitcher/LanguageSwitcher.tsx"
  ]
}
