From b303857f1e6a5509f9cb22f2592f7bf4f2961958 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Wed, 25 Feb 2026 16:18:47 +0100 Subject: [PATCH] Fix isRequired not passed to React Aria field components isRequired was consumed locally for the secondary label but never forwarded to the underlying AriaTextField/AriaSearchField, resulting in missing aria-required attribute and no built-in validation. Signed-off-by: Johan Persson --- .changeset/tired-clocks-repeat.md | 7 +++++++ packages/ui/report.api.md | 12 +++--------- .../src/components/PasswordField/PasswordField.tsx | 13 +++---------- .../ui/src/components/PasswordField/definition.ts | 1 - packages/ui/src/components/PasswordField/types.ts | 3 +-- .../ui/src/components/SearchField/SearchField.tsx | 3 +-- .../ui/src/components/SearchField/definition.ts | 1 - packages/ui/src/components/SearchField/types.ts | 6 +----- packages/ui/src/components/TextField/TextField.tsx | 13 +++---------- packages/ui/src/components/TextField/definition.ts | 1 - packages/ui/src/components/TextField/types.ts | 3 +-- 11 files changed, 20 insertions(+), 43 deletions(-) create mode 100644 .changeset/tired-clocks-repeat.md diff --git a/.changeset/tired-clocks-repeat.md b/.changeset/tired-clocks-repeat.md new file mode 100644 index 0000000000..ceea3be04f --- /dev/null +++ b/.changeset/tired-clocks-repeat.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed `isRequired` prop not being passed to the underlying React Aria field components in TextField, SearchField, and PasswordField. Previously, `isRequired` was consumed locally for the secondary label text but never forwarded, which meant the input elements lacked `aria-required="true"` and React Aria's built-in required validation was not activated. + +**Affected components:** TextField, SearchField, PasswordField diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index 226fd02738..0b96d6b085 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -1642,7 +1642,6 @@ export const PasswordFieldDefinition: { readonly label: {}; readonly description: {}; readonly secondaryLabel: {}; - readonly isRequired: {}; }; }; @@ -1655,12 +1654,11 @@ export type PasswordFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; // @public (undocumented) export interface PasswordFieldProps - extends Omit, + extends Omit, PasswordFieldOwnProps {} // @public @@ -1843,7 +1841,6 @@ export const SearchFieldDefinition: { readonly label: {}; readonly description: {}; readonly secondaryLabel: {}; - readonly isRequired: {}; }; }; @@ -1857,12 +1854,11 @@ export type SearchFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; // @public (undocumented) export interface SearchFieldProps - extends Omit, + extends Omit, SearchFieldOwnProps {} // @public (undocumented) @@ -2318,7 +2314,6 @@ export const TextFieldDefinition: { readonly label: {}; readonly description: {}; readonly secondaryLabel: {}; - readonly isRequired: {}; }; }; @@ -2331,12 +2326,11 @@ export type TextFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; // @public (undocumented) export interface TextFieldProps - extends Omit, + extends Omit, TextFieldOwnProps { type?: 'text' | 'email' | 'tel' | 'url'; } diff --git a/packages/ui/src/components/PasswordField/PasswordField.tsx b/packages/ui/src/components/PasswordField/PasswordField.tsx index 721341a5d4..7dd10f8536 100644 --- a/packages/ui/src/components/PasswordField/PasswordField.tsx +++ b/packages/ui/src/components/PasswordField/PasswordField.tsx @@ -35,15 +35,8 @@ export const PasswordField = forwardRef( PasswordFieldDefinition, props, ); - const { - classes, - label, - icon, - isRequired, - secondaryLabel, - placeholder, - description, - } = ownProps; + const { classes, label, icon, secondaryLabel, placeholder, description } = + ownProps; useEffect(() => { if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) { @@ -55,7 +48,7 @@ export const PasswordField = forwardRef( // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. const secondaryLabelText = - secondaryLabel || (isRequired ? 'Required' : null); + secondaryLabel || (restProps.isRequired ? 'Required' : null); // Manage secret visibility toggle const [isVisible, setIsVisible] = useState(false); diff --git a/packages/ui/src/components/PasswordField/definition.ts b/packages/ui/src/components/PasswordField/definition.ts index d34a740d78..7133969eff 100644 --- a/packages/ui/src/components/PasswordField/definition.ts +++ b/packages/ui/src/components/PasswordField/definition.ts @@ -40,7 +40,6 @@ export const PasswordFieldDefinition = defineComponent()( label: {}, description: {}, secondaryLabel: {}, - isRequired: {}, }, }, ); diff --git a/packages/ui/src/components/PasswordField/types.ts b/packages/ui/src/components/PasswordField/types.ts index 6b29317802..882b0320e9 100644 --- a/packages/ui/src/components/PasswordField/types.ts +++ b/packages/ui/src/components/PasswordField/types.ts @@ -42,10 +42,9 @@ export type PasswordFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; /** @public */ export interface PasswordFieldProps - extends Omit, + extends Omit, PasswordFieldOwnProps {} diff --git a/packages/ui/src/components/SearchField/SearchField.tsx b/packages/ui/src/components/SearchField/SearchField.tsx index 846506ff70..56c838833a 100644 --- a/packages/ui/src/components/SearchField/SearchField.tsx +++ b/packages/ui/src/components/SearchField/SearchField.tsx @@ -39,7 +39,6 @@ export const SearchField = forwardRef( classes, label, icon, - isRequired, secondaryLabel, placeholder, startCollapsed, @@ -59,7 +58,7 @@ export const SearchField = forwardRef( // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. const secondaryLabelText = - secondaryLabel || (isRequired ? 'Required' : null); + secondaryLabel || (restProps.isRequired ? 'Required' : null); const handleFocusChange = (isFocused: boolean) => { restProps.onFocusChange?.(isFocused); diff --git a/packages/ui/src/components/SearchField/definition.ts b/packages/ui/src/components/SearchField/definition.ts index c0c7ac8590..76134ed0bb 100644 --- a/packages/ui/src/components/SearchField/definition.ts +++ b/packages/ui/src/components/SearchField/definition.ts @@ -40,6 +40,5 @@ export const SearchFieldDefinition = defineComponent()({ label: {}, description: {}, secondaryLabel: {}, - isRequired: {}, }, }); diff --git a/packages/ui/src/components/SearchField/types.ts b/packages/ui/src/components/SearchField/types.ts index 131eaae8c5..6f900c5ee3 100644 --- a/packages/ui/src/components/SearchField/types.ts +++ b/packages/ui/src/components/SearchField/types.ts @@ -47,13 +47,9 @@ export type SearchFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; /** @public */ export interface SearchFieldProps - extends Omit< - AriaSearchFieldProps, - 'className' | 'isRequired' | 'description' - >, + extends Omit, SearchFieldOwnProps {} diff --git a/packages/ui/src/components/TextField/TextField.tsx b/packages/ui/src/components/TextField/TextField.tsx index 88fb006b60..acea45722f 100644 --- a/packages/ui/src/components/TextField/TextField.tsx +++ b/packages/ui/src/components/TextField/TextField.tsx @@ -29,15 +29,8 @@ export const TextField = forwardRef( TextFieldDefinition, props, ); - const { - classes, - label, - icon, - isRequired, - secondaryLabel, - placeholder, - description, - } = ownProps; + const { classes, label, icon, secondaryLabel, placeholder, description } = + ownProps; useEffect(() => { if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) { @@ -49,7 +42,7 @@ export const TextField = forwardRef( // If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required. const secondaryLabelText = - secondaryLabel || (isRequired ? 'Required' : null); + secondaryLabel || (restProps.isRequired ? 'Required' : null); return ( ()({ label: {}, description: {}, secondaryLabel: {}, - isRequired: {}, }, }); diff --git a/packages/ui/src/components/TextField/types.ts b/packages/ui/src/components/TextField/types.ts index c436c17937..e65473d0c9 100644 --- a/packages/ui/src/components/TextField/types.ts +++ b/packages/ui/src/components/TextField/types.ts @@ -42,12 +42,11 @@ export type TextFieldOwnProps = { label?: FieldLabelProps['label']; description?: FieldLabelProps['description']; secondaryLabel?: FieldLabelProps['secondaryLabel']; - isRequired?: boolean; }; /** @public */ export interface TextFieldProps - extends Omit, + extends Omit, TextFieldOwnProps { /** * The HTML input type for the text field