From e99948ba1558d790ab52b0af353e79926714ee08 Mon Sep 17 00:00:00 2001 From: Airike Jaska <95303654+airikej@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:54:00 +0300 Subject: [PATCH 1/4] fix(select): wcag fixes #58 --- .../form/select/components/select-input.tsx | 35 ++++++++++++++----- .../components/select-multi-value-remove.tsx | 2 +- .../select/components/select-multi-value.tsx | 7 ++++ .../components/form/select/select.module.scss | 16 +++------ .../components/form/select/select.spec.tsx | 33 +++++++++++++++-- .../components/form/select/select.stories.tsx | 4 +-- src/tedi/components/form/select/select.tsx | 5 ++- 7 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/tedi/components/form/select/components/select-input.tsx b/src/tedi/components/form/select/components/select-input.tsx index bd58ce839..3d83ccbd8 100644 --- a/src/tedi/components/form/select/components/select-input.tsx +++ b/src/tedi/components/form/select/components/select-input.tsx @@ -4,12 +4,29 @@ import { components as ReactSelectComponents, InputProps } from 'react-select'; import { ISelectOption } from '../select'; import styles from '../select.module.scss'; -export const SelectInput = (props: InputProps): JSX.Element => ( - -); +export const SelectInput = (props: InputProps): JSX.Element => { + // react-select points the combobox's `aria-describedby` at its own placeholder + // element, so screen readers announce the placeholder as the field's + // description (placeholder should be visual only). Drop that reference, keep any + // other react-select description (e.g. its live-region), and append the field's + // helper/error text so it's actually announced. + const helperDescribedBy = (props.selectProps as { 'aria-describedby'?: string })['aria-describedby']; + const describedBy = + [ + ...(props['aria-describedby']?.split(' ') ?? []).filter((token) => token && !token.endsWith('-placeholder')), + helperDescribedBy, + ] + .filter(Boolean) + .join(' ') || undefined; + + return ( + + ); +}; diff --git a/src/tedi/components/form/select/components/select-multi-value-remove.tsx b/src/tedi/components/form/select/components/select-multi-value-remove.tsx index 828e3a88b..aef655096 100644 --- a/src/tedi/components/form/select/components/select-multi-value-remove.tsx +++ b/src/tedi/components/form/select/components/select-multi-value-remove.tsx @@ -29,7 +29,7 @@ export const SelectMultiValueRemove = ({ innerProps, data }: MultiValueRemovePro onKeyDown={handleKeyDown} className={styles['tedi-select__multi-value-clear']} iconSize={18} - title={`${getLabel('clear')} ${data.label}`} + title={`${getLabel('remove')} ${typeof data.label === 'string' ? data.label : data.value}`} /> diff --git a/src/tedi/components/form/select/components/select-multi-value.tsx b/src/tedi/components/form/select/components/select-multi-value.tsx index b58f96fa5..8c90a0ccf 100644 --- a/src/tedi/components/form/select/components/select-multi-value.tsx +++ b/src/tedi/components/form/select/components/select-multi-value.tsx @@ -1,6 +1,7 @@ import cn from 'classnames'; import { MultiValueProps } from 'react-select'; +import { useLabels } from '../../../../providers/label-provider'; import { Tag, TagEllipsis } from '../../../tags/tag/tag'; import { ISelectOption } from '../select'; import styles from '../select.module.scss'; @@ -47,6 +48,7 @@ export const SelectMultiValue = ({ removeProps, ...props }: MultiValueType): JSX.Element | null => { + const { getLabel } = useLabels(); const { isSingleRow, visibleCount } = useSelectTagsContext(); if (props.data.value === SELECT_ALL_VALUE || isGroupSentinel(props.data)) { @@ -88,6 +90,11 @@ export const SelectMultiValue = ({ isTagRemovable ? { tabIndex: 0, + // Name the button "Remove