From b9d1446a5c94b09d0a01ac750283dd42f28caed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Mon, 20 Jul 2026 14:29:51 +0200 Subject: [PATCH] Preserve Field aria-describedby on errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloning the control overwrote any existing description ids. Merge the error id in so hints stay announced. Signed-off-by: Émile Ré --- packages/ui/src/v2/form/Field.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/v2/form/Field.tsx b/packages/ui/src/v2/form/Field.tsx index a4aaa63d8..a09e4ea1a 100644 --- a/packages/ui/src/v2/form/Field.tsx +++ b/packages/ui/src/v2/form/Field.tsx @@ -30,7 +30,8 @@ export type FieldProps = { error?: ReactNode; className?: string; // A single form control (TextField, Textarea, …). It receives an injected - // `id`, plus `aria-describedby`/`aria-invalid` when an error is present. + // `id`, plus `aria-invalid` and a merged `aria-describedby` when an error + // is present (existing description IDs are kept). children: ReactNode; }; @@ -51,11 +52,22 @@ export function Field(props: FieldProps) { const existingId = typeof child?.props.id === "string" ? child.props.id : undefined; const controlId = existingId ?? generatedId; + const existingDescribedBy + = typeof child?.props["aria-describedby"] === "string" + ? child.props["aria-describedby"].trim() || undefined + : undefined; + // Append the error id without dropping hint / help description ids the + // control already exposes. Omit the prop when there is no error so an + // existing value is not wiped by cloneElement. + const describedBy = error != null + ? [existingDescribedBy, errorId].filter(Boolean).join(" ") + : undefined; + const control = child ? cloneElement(child, { - "id": controlId, - "aria-describedby": error != null ? errorId : undefined, - "aria-invalid": error != null ? true : undefined, + id: controlId, + ...(describedBy != null ? { "aria-describedby": describedBy } : {}), + ...(error != null ? { "aria-invalid": true as const } : {}), }) : children;