From af2c084b3387caf06d430b1c5915a69af0be5d0d Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Fri, 19 Sep 2025 13:28:11 +0200 Subject: [PATCH 1/9] fix: connect combobox input to validation message, set aria-invalid --- .../src/components/ComboboxWrapper.tsx | 7 ++++--- .../MultiSelection/MultiSelection.tsx | 3 +++ .../SingleSelection/SingleSelection.tsx | 6 +++++- .../widget-plugin-component-kit/src/Alert.tsx | 19 +++++++++++++++---- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx index fcb8b380b2..f479ed3927 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx @@ -14,8 +14,8 @@ interface ComboboxWrapperProps extends PropsWithChildren { validation?: string; isLoading: boolean; isMultiselectActive?: boolean; + inputId: string; } - export const ComboboxWrapper = forwardRef( (props: ComboboxWrapperProps, ref: RefObject): ReactElement => { const { @@ -26,7 +26,8 @@ export const ComboboxWrapper = forwardRef( validation, children, isLoading, - isMultiselectActive + isMultiselectActive, + inputId } = props; const { id, onClick } = getToggleButtonProps(); @@ -56,7 +57,7 @@ export const ComboboxWrapper = forwardRef( )} - {validation && {validation}} + {validation && {validation}} ); } diff --git a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx index baa56c606d..f1e3bf279d 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx @@ -96,6 +96,7 @@ export function MultiSelection({ validation={selector.validation} isLoading={lazyLoading && selector.options.isLoading} isMultiselectActive={selectedItems?.length > 0} + inputId={options.inputId} >
{memoizedselectedCaptions}
diff --git a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx index 3e23bee840..6fbe663602 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx @@ -69,7 +69,8 @@ export function SingleSelection({ }, { suppressRefError: true } ); - + console.log(inputProps); + console.log(options); return (
( - +export const ValidationAlert = ({ className, children, referenceId }: ValidationAlertProps): ReactElement => ( + {children} ); -export const Alert = ({ className, bootstrapStyle, children, role }: AlertProps): ReactNode => +export const Alert = ({ className, bootstrapStyle, children, role, inputElementId }: AlertProps): ReactNode => Children.count(children) > 0 ? ( -
+
{children}
) : null; From bb5ba3f694d10c8d115591f7f9861c54a669bee8 Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Fri, 19 Sep 2025 13:39:16 +0200 Subject: [PATCH 2/9] chore: add changelog --- packages/pluggableWidgets/combobox-web/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pluggableWidgets/combobox-web/CHANGELOG.md b/packages/pluggableWidgets/combobox-web/CHANGELOG.md index 1a970c9dd4..c8c3386633 100644 --- a/packages/pluggableWidgets/combobox-web/CHANGELOG.md +++ b/packages/pluggableWidgets/combobox-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed + +- We fixed an issue where validation was not connected to combobox, causing issues for screenreaders. + ## [2.6.0] - 2025-09-26 ### Changed From 56d74155eb7ce8267e2e9356293248de61b9dc60 Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Fri, 19 Sep 2025 13:51:08 +0200 Subject: [PATCH 3/9] fix: remove console logs --- .../src/components/SingleSelection/SingleSelection.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx index 6fbe663602..701f5dfbb1 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx @@ -69,8 +69,6 @@ export function SingleSelection({ }, { suppressRefError: true } ); - console.log(inputProps); - console.log(options); return ( Date: Fri, 19 Sep 2025 15:28:18 +0200 Subject: [PATCH 4/9] fix: change aria-invalid to only show up when it's true --- .../src/components/MultiSelection/MultiSelection.tsx | 2 +- .../src/components/SingleSelection/SingleSelection.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx index f1e3bf279d..ec9d4fb6b0 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx @@ -141,7 +141,7 @@ export function MultiSelection({ {...inputProps} aria-labelledby={hasLabel ? inputProps["aria-labelledby"] : undefined} aria-describedby={selector.validation ? options.inputId + "-error" : undefined} - aria-invalid={!!selector.validation} + aria-invalid={selector.validation ? true : undefined} /> {memoizedselectedCaptions}
diff --git a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx index 701f5dfbb1..eafeea7de0 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx @@ -94,7 +94,7 @@ export function SingleSelection({ placeholder=" " aria-labelledby={hasLabel ? inputProps["aria-labelledby"] : undefined} aria-describedby={selector.validation ? options.inputId + "-error" : undefined} - aria-invalid={!!selector.validation} + aria-invalid={selector.validation ? true : undefined} /> Date: Fri, 19 Sep 2025 16:04:12 +0200 Subject: [PATCH 5/9] fix: make id on alert conditional on if there's an inputelementid --- packages/shared/widget-plugin-component-kit/src/Alert.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/widget-plugin-component-kit/src/Alert.tsx b/packages/shared/widget-plugin-component-kit/src/Alert.tsx index 91e23fd709..1b4cddeb3e 100644 --- a/packages/shared/widget-plugin-component-kit/src/Alert.tsx +++ b/packages/shared/widget-plugin-component-kit/src/Alert.tsx @@ -32,7 +32,7 @@ export const Alert = ({ className, bootstrapStyle, children, role, inputElementI
{children}
From bdf58af8fda92fb937129fe0a5119e1d7455dade Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Tue, 23 Sep 2025 15:09:56 +0200 Subject: [PATCH 6/9] fix: add error suffix to combobox instead of in alert, rename id prop --- .../combobox-web/src/components/ComboboxWrapper.tsx | 2 +- .../shared/widget-plugin-component-kit/src/Alert.tsx | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx index f479ed3927..970c93ac84 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx @@ -57,7 +57,7 @@ export const ComboboxWrapper = forwardRef(
)} - {validation && {validation}} + {validation && {validation}}
); } diff --git a/packages/shared/widget-plugin-component-kit/src/Alert.tsx b/packages/shared/widget-plugin-component-kit/src/Alert.tsx index 1b4cddeb3e..00cbdebc32 100644 --- a/packages/shared/widget-plugin-component-kit/src/Alert.tsx +++ b/packages/shared/widget-plugin-component-kit/src/Alert.tsx @@ -5,7 +5,7 @@ export interface AlertProps { children?: ReactNode; className?: string; bootstrapStyle: "default" | "primary" | "success" | "info" | "warning" | "danger"; - inputElementId?: string; + id?: string; role?: string; } @@ -21,19 +21,15 @@ export const ValidationAlert = ({ className, children, referenceId }: Validation className={classNames("mx-validation-message", className)} bootstrapStyle="danger" role="alert" - inputElementId={referenceId} + id={referenceId} > {children} ); -export const Alert = ({ className, bootstrapStyle, children, role, inputElementId }: AlertProps): ReactNode => +export const Alert = ({ className, bootstrapStyle, children, role, id }: AlertProps): ReactNode => Children.count(children) > 0 ? ( -
+
{children}
) : null; From bed27b4881a32975f9d95e6940219e28323c4959 Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Tue, 23 Sep 2025 15:34:10 +0200 Subject: [PATCH 7/9] refactor: rewrite validation id handling to be less tightly coupled --- .../combobox-web/src/components/ComboboxWrapper.tsx | 6 +++--- .../src/components/MultiSelection/MultiSelection.tsx | 5 +++-- .../src/components/SingleSelection/SingleSelection.tsx | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx index 970c93ac84..8faa9cbb12 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx @@ -14,7 +14,7 @@ interface ComboboxWrapperProps extends PropsWithChildren { validation?: string; isLoading: boolean; isMultiselectActive?: boolean; - inputId: string; + errorId?: string; } export const ComboboxWrapper = forwardRef( (props: ComboboxWrapperProps, ref: RefObject): ReactElement => { @@ -27,7 +27,7 @@ export const ComboboxWrapper = forwardRef( children, isLoading, isMultiselectActive, - inputId + errorId } = props; const { id, onClick } = getToggleButtonProps(); @@ -57,7 +57,7 @@ export const ComboboxWrapper = forwardRef(
)} - {validation && {validation}} + {validation && {validation}} ); } diff --git a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx index ec9d4fb6b0..0e7bbb2fb0 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx @@ -86,6 +86,7 @@ export function MultiSelection({ readOnly: selector.readOnly }); + const errorId = options.inputId ? options.inputId + "-validation-message" : undefined; return ( 0} - inputId={options.inputId} + errorId={errorId} >
{memoizedselectedCaptions} diff --git a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx index eafeea7de0..011db0c589 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx @@ -29,6 +29,7 @@ export function SingleSelection({ highlightedIndex } = useDownshiftSingleSelectProps(selector, options, a11yConfig.a11yStatusMessage); const inputRef = useRef(null); + const errorId = options.inputId ? options.inputId + "-validation-message" : undefined; const lazyLoading = selector.lazyLoading ?? false; const { onScroll } = useLazyLoading({ hasMoreItems: selector.options.hasMore ?? false, @@ -78,7 +79,7 @@ export function SingleSelection({ getToggleButtonProps={getToggleButtonProps} validation={selector.validation} isLoading={lazyLoading && selector.options.isLoading} - inputId={options.inputId} + errorId={errorId} >
Date: Thu, 25 Sep 2025 10:27:44 +0200 Subject: [PATCH 8/9] feat: add getValidationErrorId utility function --- .../src/components/MultiSelection/MultiSelection.tsx | 4 ++-- .../src/components/SingleSelection/SingleSelection.tsx | 4 ++-- packages/pluggableWidgets/combobox-web/src/helpers/utils.ts | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx index 0e7bbb2fb0..e16176c1bd 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx @@ -2,7 +2,7 @@ import classNames from "classnames"; import { createElement, Fragment, KeyboardEvent, ReactElement, useMemo, useRef } from "react"; import { ClearButton } from "../../assets/icons"; import { MultiSelector, SelectionBaseProps } from "../../helpers/types"; -import { getInputLabel, getSelectedCaptionsPlaceholder } from "../../helpers/utils"; +import { getInputLabel, getSelectedCaptionsPlaceholder, getValidationErrorId } from "../../helpers/utils"; import { useDownshiftMultiSelectProps } from "../../hooks/useDownshiftMultiSelectProps"; import { useLazyLoading } from "../../hooks/useLazyLoading"; import { ComboboxWrapper } from "../ComboboxWrapper"; @@ -38,6 +38,7 @@ export function MultiSelection({ const isSelectedItemsBoxStyle = selector.selectedItemsStyle === "boxes"; const isOptionsSelected = selector.isOptionsSelected(); const inputLabel = getInputLabel(options.inputId); + const errorId = getValidationErrorId(options.inputId); const hasLabel = useMemo(() => Boolean(inputLabel), [inputLabel]); const inputProps = getInputProps({ ...getDropdownProps( @@ -86,7 +87,6 @@ export function MultiSelection({ readOnly: selector.readOnly }); - const errorId = options.inputId ? options.inputId + "-validation-message" : undefined; return ( (null); - const errorId = options.inputId ? options.inputId + "-validation-message" : undefined; const lazyLoading = selector.lazyLoading ?? false; const { onScroll } = useLazyLoading({ hasMoreItems: selector.options.hasMore ?? false, @@ -58,6 +57,7 @@ export function SingleSelection({ ); const inputLabel = getInputLabel(options.inputId); + const errorId = getValidationErrorId(options.inputId); const hasLabel = useMemo(() => Boolean(inputLabel), [inputLabel]); const inputProps = getInputProps( diff --git a/packages/pluggableWidgets/combobox-web/src/helpers/utils.ts b/packages/pluggableWidgets/combobox-web/src/helpers/utils.ts index aa48dba332..51dd54cd62 100644 --- a/packages/pluggableWidgets/combobox-web/src/helpers/utils.ts +++ b/packages/pluggableWidgets/combobox-web/src/helpers/utils.ts @@ -153,3 +153,7 @@ function sortSelections( export function getInputLabel(inputId: string): Element | null { return document.querySelector(`label[for="${inputId}"]`); } + +export function getValidationErrorId(inputId?: string): string | undefined { + return inputId ? inputId + "-validation-message" : undefined; +} From b215c2f5dd4a6878f45b50b8ec05304696160c48 Mon Sep 17 00:00:00 2001 From: Hedwig Doets Date: Thu, 2 Oct 2025 14:10:23 +0200 Subject: [PATCH 9/9] fix: rename referenceId to id in ValidationAlertProps --- .../combobox-web/src/components/ComboboxWrapper.tsx | 2 +- .../shared/widget-plugin-component-kit/src/Alert.tsx | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx index 8faa9cbb12..7138d396ba 100644 --- a/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx +++ b/packages/pluggableWidgets/combobox-web/src/components/ComboboxWrapper.tsx @@ -57,7 +57,7 @@ export const ComboboxWrapper = forwardRef(
)}
- {validation && {validation}} + {validation && {validation}}
); } diff --git a/packages/shared/widget-plugin-component-kit/src/Alert.tsx b/packages/shared/widget-plugin-component-kit/src/Alert.tsx index 00cbdebc32..c7b1485018 100644 --- a/packages/shared/widget-plugin-component-kit/src/Alert.tsx +++ b/packages/shared/widget-plugin-component-kit/src/Alert.tsx @@ -12,17 +12,12 @@ export interface AlertProps { export interface ValidationAlertProps { children?: ReactNode; className?: string; - referenceId?: string; + id?: string; } // cloning from https://gitlab.rnd.mendix.com/appdev/appdev/-/blob/master/client/src/widgets/web/helpers/Alert.tsx -export const ValidationAlert = ({ className, children, referenceId }: ValidationAlertProps): ReactElement => ( - +export const ValidationAlert = ({ className, children, id }: ValidationAlertProps): ReactElement => ( + {children} );