From b4113de74a6cc832929882a2f3a18297ebd4522d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:01:45 +0200 Subject: [PATCH] Accessibility fix pass for twenty-ui input components (#21776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-enables the live Storybook axe gate across the twenty-ui `input` domain (17 story files) by removing the inherited `a11y: { test: 'todo' }` overrides. Pilot for the wider a11y fix pass; other domains follow the same playbook. **What changed** - Accessible names added to icon-only buttons, `Toggle`, `Checkbox`, and the button groups via small additive `aria-label`/`ariaLabel` props on 8 components. Where this fully fixes the story, the override is dropped so the full gate applies. - Color-contrast (design-token level) is deferred — not changed — via a new shared `A11Y_DEFER_COLOR_CONTRAST` parameter (`@ui/testing`) that disables only the `color-contrast` rule while every other axe rule stays enforced. Grep the constant to find all deferrals when tokens are darkened later. - `CatalogDecorator`: unique cell ids (clears `duplicate-id-aria`) and dimension titles switched from empty `h1/h2/h3` to `div` (clears `empty-heading`). Both help every domain's catalogs. **Reviewer notes** - No visual change: edits are aria / id / story-args / axe-config only, so Argos parity holds. - Input axe gate goes from 12 failing to 60/60 passing; typecheck and lint green. Review in cubic --- .../AnimatedLightIconButton.stories.tsx | 4 +-- .../Button/__stories__/Button.stories.tsx | 16 +++++------- .../__stories__/ButtonGroup.stories.tsx | 7 +++-- .../twenty-ui/src/input/Checkbox/Checkbox.tsx | 3 +++ .../Checkbox/__stories__/Checkbox.stories.tsx | 9 +++---- .../ColorPickerButton/ColorPickerButton.tsx | 1 + .../__stories__/ColorPickerButton.stories.tsx | 7 +---- .../input/FloatingButton/FloatingButton.tsx | 3 +++ .../__stories__/FloatingButton.stories.tsx | 4 +-- .../FloatingButtonGroup.stories.tsx | 26 ++++++++++++------- .../FloatingIconButton/FloatingIconButton.tsx | 3 +++ .../FloatingIconButton.stories.tsx | 9 +++---- .../FloatingIconButtonGroup.tsx | 4 ++- .../FloatingIconButtonGroup.stories.tsx | 12 ++++----- .../__stories__/IconButton.stories.tsx | 14 +++++----- .../input/IconButtonGroup/IconButtonGroup.tsx | 4 ++- .../__stories__/IconButtonGroup.stories.tsx | 10 +++---- .../src/input/InsideButton/InsideButton.tsx | 3 +++ .../__stories__/LightButton.stories.tsx | 4 +-- .../__stories__/LightIconButton.stories.tsx | 4 +-- .../input/Radio/__stories__/Radio.stories.tsx | 4 +-- .../RoundedIconButton/RoundedIconButton.tsx | 2 ++ .../__stories__/RoundedIconButton.stories.tsx | 4 +-- .../__stories__/TabButton.stories.tsx | 4 +-- .../Toggle/__stories__/Toggle.stories.tsx | 7 +++-- .../twenty-ui/src/testing/a11yParameters.ts | 6 +++++ .../testing/decorators/CatalogDecorator.tsx | 18 ++++++++----- packages/twenty-ui/src/testing/index.ts | 1 + 28 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 packages/twenty-ui/src/testing/a11yParameters.ts diff --git a/packages/twenty-ui/src/input/AnimatedLightIconButton/__stories__/AnimatedLightIconButton.stories.tsx b/packages/twenty-ui/src/input/AnimatedLightIconButton/__stories__/AnimatedLightIconButton.stories.tsx index 3fad775eb8..8bc7b9ba4b 100644 --- a/packages/twenty-ui/src/input/AnimatedLightIconButton/__stories__/AnimatedLightIconButton.stories.tsx +++ b/packages/twenty-ui/src/input/AnimatedLightIconButton/__stories__/AnimatedLightIconButton.stories.tsx @@ -5,6 +5,7 @@ import { type LightIconButtonSize, } from '@ui/input/LightIconButton/LightIconButton'; import { + A11Y_DEFER_COLOR_CONTRAST, CatalogDecorator, type CatalogStory, ComponentDecorator, @@ -54,8 +55,7 @@ export const Catalog: CatalogStory = { transition: { control: 'object' }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'] }, catalog: { dimensions: [ diff --git a/packages/twenty-ui/src/input/Button/__stories__/Button.stories.tsx b/packages/twenty-ui/src/input/Button/__stories__/Button.stories.tsx index 9d9379b2bf..950eb091a6 100644 --- a/packages/twenty-ui/src/input/Button/__stories__/Button.stories.tsx +++ b/packages/twenty-ui/src/input/Button/__stories__/Button.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { IconReload, IconSearch } from '@ui/icon'; import { + A11Y_DEFER_COLOR_CONTRAST, CatalogDecorator, type CatalogStory, ComponentDecorator, @@ -22,8 +23,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, + parameters: { a11y: A11Y_DEFER_COLOR_CONTRAST }, argTypes: { hotkeys: { control: false }, Icon: { control: false }, @@ -61,8 +61,7 @@ export const Catalog: CatalogStory = { isLoading: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ @@ -136,8 +135,7 @@ export const SoonCatalog: CatalogStory = { hotkeys: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ @@ -210,8 +208,7 @@ export const PositionCatalog: CatalogStory = { hotkeys: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ @@ -288,8 +285,7 @@ export const ShortcutCatalog: CatalogStory = { position: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ diff --git a/packages/twenty-ui/src/input/ButtonGroup/__stories__/ButtonGroup.stories.tsx b/packages/twenty-ui/src/input/ButtonGroup/__stories__/ButtonGroup.stories.tsx index 74bd98a3cb..4935105071 100644 --- a/packages/twenty-ui/src/input/ButtonGroup/__stories__/ButtonGroup.stories.tsx +++ b/packages/twenty-ui/src/input/ButtonGroup/__stories__/ButtonGroup.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { IconCheckbox, IconNotes, IconTimelineEvent } from '@ui/icon'; import { + A11Y_DEFER_COLOR_CONTRAST, CatalogDecorator, type CatalogStory, ComponentDecorator, @@ -22,8 +23,7 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, + parameters: { a11y: A11Y_DEFER_COLOR_CONTRAST }, args: { size: 'small', variant: 'primary', @@ -55,8 +55,7 @@ export const Catalog: CatalogStory = { children: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ diff --git a/packages/twenty-ui/src/input/Checkbox/Checkbox.tsx b/packages/twenty-ui/src/input/Checkbox/Checkbox.tsx index 4057c8bc6d..95e8c9646f 100644 --- a/packages/twenty-ui/src/input/Checkbox/Checkbox.tsx +++ b/packages/twenty-ui/src/input/Checkbox/Checkbox.tsx @@ -39,6 +39,7 @@ type CheckboxProps = { className?: string; disabled?: boolean; accent?: CheckboxAccent; + 'aria-label'?: string; }; export const Checkbox = ({ @@ -53,6 +54,7 @@ export const Checkbox = ({ className, disabled = false, accent = CheckboxAccent.Blue, + 'aria-label': ariaLabel, }: CheckboxProps) => { const [isInternalChecked, setIsInternalChecked] = React.useState(false); @@ -67,6 +69,7 @@ export const Checkbox = ({ indeterminate={indeterminate} disabled={disabled} name="styled-checkbox" + aria-label={ariaLabel} data-testid="input-checkbox" onCheckedChange={(value, eventDetails) => { onChange?.( diff --git a/packages/twenty-ui/src/input/Checkbox/__stories__/Checkbox.stories.tsx b/packages/twenty-ui/src/input/Checkbox/__stories__/Checkbox.stories.tsx index 93105dfe3e..ef7a4ad79d 100644 --- a/packages/twenty-ui/src/input/Checkbox/__stories__/Checkbox.stories.tsx +++ b/packages/twenty-ui/src/input/Checkbox/__stories__/Checkbox.stories.tsx @@ -1,5 +1,6 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { + A11Y_DEFER_COLOR_CONTRAST, CatalogDecorator, type CatalogStory, ComponentDecorator, @@ -22,8 +23,6 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, args: { checked: false, indeterminate: false, @@ -33,12 +32,13 @@ export const Default: Story = { size: CheckboxSize.Small, shape: CheckboxShape.Squared, accent: CheckboxAccent.Blue, + 'aria-label': 'Checkbox', }, decorators: [ComponentDecorator], }; export const Catalog: CatalogStory = { - args: {}, + args: { 'aria-label': 'Checkbox' }, argTypes: { variant: { control: false }, size: { control: false }, @@ -49,8 +49,7 @@ export const Catalog: CatalogStory = { accent: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, catalog: { dimensions: [ { diff --git a/packages/twenty-ui/src/input/ColorPickerButton/ColorPickerButton.tsx b/packages/twenty-ui/src/input/ColorPickerButton/ColorPickerButton.tsx index b9d451811b..af8ccc1948 100644 --- a/packages/twenty-ui/src/input/ColorPickerButton/ColorPickerButton.tsx +++ b/packages/twenty-ui/src/input/ColorPickerButton/ColorPickerButton.tsx @@ -21,6 +21,7 @@ export const ColorPickerButton = ({ } + aria-label={`Select ${colorName} color`} onClick={onClick} /> diff --git a/packages/twenty-ui/src/input/ColorPickerButton/__stories__/ColorPickerButton.stories.tsx b/packages/twenty-ui/src/input/ColorPickerButton/__stories__/ColorPickerButton.stories.tsx index 63540cae6a..d8935908d7 100644 --- a/packages/twenty-ui/src/input/ColorPickerButton/__stories__/ColorPickerButton.stories.tsx +++ b/packages/twenty-ui/src/input/ColorPickerButton/__stories__/ColorPickerButton.stories.tsx @@ -12,13 +12,8 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, -}; +export const Default: Story = {}; export const Selected: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, args: { isSelected: true }, }; diff --git a/packages/twenty-ui/src/input/FloatingButton/FloatingButton.tsx b/packages/twenty-ui/src/input/FloatingButton/FloatingButton.tsx index 6a97ee5764..cd002a426a 100644 --- a/packages/twenty-ui/src/input/FloatingButton/FloatingButton.tsx +++ b/packages/twenty-ui/src/input/FloatingButton/FloatingButton.tsx @@ -14,6 +14,7 @@ export type FloatingButtonProps = { className?: string; Icon?: IconComponent; title?: string; + ariaLabel?: string; size?: FloatingButtonSize; position?: FloatingButtonPosition; applyShadow?: boolean; @@ -27,6 +28,7 @@ export const FloatingButton = ({ className, Icon, title, + ariaLabel, size = 'small', position = 'standalone', applyBlur = true, @@ -46,6 +48,7 @@ export const FloatingButton = ({ return ( = { focus: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'] }, catalog: { dimensions: [ diff --git a/packages/twenty-ui/src/input/FloatingButtonGroup/__stories__/FloatingButtonGroup.stories.tsx b/packages/twenty-ui/src/input/FloatingButtonGroup/__stories__/FloatingButtonGroup.stories.tsx index 0be3985fbb..93433c7b79 100644 --- a/packages/twenty-ui/src/input/FloatingButtonGroup/__stories__/FloatingButtonGroup.stories.tsx +++ b/packages/twenty-ui/src/input/FloatingButtonGroup/__stories__/FloatingButtonGroup.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { IconCheckbox, IconNotes, IconTimelineEvent } from '@ui/icon'; import { + A11Y_DEFER_COLOR_CONTRAST, CatalogDecorator, type CatalogStory, ComponentDecorator, @@ -20,14 +21,16 @@ export default meta; type Story = StoryObj; export const Default: Story = { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - parameters: { a11y: { test: 'todo' } }, args: { size: 'small', children: [ - , - , - , + , + , + , ], }, argTypes: { @@ -39,9 +42,13 @@ export const Default: Story = { export const Catalog: CatalogStory = { args: { children: [ - , - , - , + , + , + , ], }, argTypes: { @@ -49,8 +56,7 @@ export const Catalog: CatalogStory = { children: { control: false }, }, parameters: { - // TODO(a11y): violations inherited from deprecated story; fix during a11y pass - a11y: { test: 'todo' }, + a11y: A11Y_DEFER_COLOR_CONTRAST, pseudo: { hover: ['.hover'], active: ['.pressed'], focus: ['.focus'] }, catalog: { dimensions: [ diff --git a/packages/twenty-ui/src/input/FloatingIconButton/FloatingIconButton.tsx b/packages/twenty-ui/src/input/FloatingIconButton/FloatingIconButton.tsx index c60cc40a9c..4fdc082d07 100644 --- a/packages/twenty-ui/src/input/FloatingIconButton/FloatingIconButton.tsx +++ b/packages/twenty-ui/src/input/FloatingIconButton/FloatingIconButton.tsx @@ -16,6 +16,7 @@ export type FloatingIconButtonPosition = export type FloatingIconButtonProps = { className?: string; Icon?: IconComponent; + ariaLabel?: string; size?: FloatingIconButtonSize; position?: FloatingIconButtonPosition; applyShadow?: boolean; @@ -29,6 +30,7 @@ export type FloatingIconButtonProps = { export const FloatingIconButton = ({ className, Icon, + ariaLabel, size = 'small', position = 'standalone', applyShadow = true, @@ -43,6 +45,7 @@ export const FloatingIconButton = ({ return (