Preserve Field aria-describedby on errors

Cloning the control overwrote any existing description
ids. Merge the error id in so hints stay announced.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-20 14:29:51 +02:00
parent 1c28f63555
commit b9d1446a5c

View File

@@ -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;