From 3af59037138790a4837324ffabf659d6735a0109 Mon Sep 17 00:00:00 2001 From: Leo Torres Date: Fri, 27 Mar 2026 12:12:39 +0100 Subject: [PATCH 1/2] fix: add radiogroup ARIA semantics to ColorPicker component Swatches now use role=radiogroup/radio with aria-checked, aria-label, roving tabindex, and arrow-key navigation for WCAG 4.1.2 compliance. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/components/forms/ColorPicker.vue | 55 ++++++--- .../src/tests/components/ColorPicker.test.js | 104 +++++++++++++++++- .../workspace-input-isolation.test.js | 6 +- 3 files changed, 144 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/forms/ColorPicker.vue b/frontend/src/components/forms/ColorPicker.vue index 7952bfc93..7e1314732 100644 --- a/frontend/src/components/forms/ColorPicker.vue +++ b/frontend/src/components/forms/ColorPicker.vue @@ -30,12 +30,13 @@ * default-active="brand-b" * /> */ - import { ref, watch } from "vue"; + import { ref, watch, computed } from "vue"; const props = defineProps({ colors: { type: Object, required: true }, defaultActive: { type: String, default: "" }, labels: { type: Boolean, default: true }, + groupLabel: { type: String, default: "Color" }, }); const emit = defineEmits(["change"]); @@ -47,28 +48,53 @@ } ); + const colorNames = computed(() => Object.keys(props.colors)); + + const tabbableColor = computed(() => { + if (activeColor.value && colorNames.value.includes(activeColor.value)) { + return activeColor.value; + } + return colorNames.value[0] || ""; + }); + const onClick = (name) => { activeColor.value = name; emit("change", name); }; + + const onKeydown = (e) => { + const names = colorNames.value; + const idx = names.indexOf(activeColor.value || names[0]); + let next; + if (e.key === "ArrowRight" || e.key === "ArrowDown") { + e.preventDefault(); + next = names[(idx + 1) % names.length]; + } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { + e.preventDefault(); + next = names[(idx - 1 + names.length) % names.length]; + } + if (next !== undefined) { + onClick(next); + } + };