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 <johanopersson@gmail.com>
This commit is contained in:
Johan Persson
2026-02-25 16:18:47 +01:00
parent 42311e3a85
commit b303857f1e
11 changed files with 20 additions and 43 deletions
+7
View File
@@ -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
+3 -9
View File
@@ -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<TextFieldProps_2, 'className' | 'isRequired' | 'description'>,
extends Omit<TextFieldProps_2, 'className' | 'description'>,
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<SearchFieldProps_2, 'className' | 'isRequired' | 'description'>,
extends Omit<SearchFieldProps_2, 'className' | 'description'>,
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<TextFieldProps_2, 'className' | 'isRequired' | 'description'>,
extends Omit<TextFieldProps_2, 'className' | 'description'>,
TextFieldOwnProps {
type?: 'text' | 'email' | 'tel' | 'url';
}
@@ -35,15 +35,8 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
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<HTMLDivElement, PasswordFieldProps>(
// 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);
@@ -40,7 +40,6 @@ export const PasswordFieldDefinition = defineComponent<PasswordFieldOwnProps>()(
label: {},
description: {},
secondaryLabel: {},
isRequired: {},
},
},
);
@@ -42,10 +42,9 @@ export type PasswordFieldOwnProps = {
label?: FieldLabelProps['label'];
description?: FieldLabelProps['description'];
secondaryLabel?: FieldLabelProps['secondaryLabel'];
isRequired?: boolean;
};
/** @public */
export interface PasswordFieldProps
extends Omit<AriaTextFieldProps, 'className' | 'isRequired' | 'description'>,
extends Omit<AriaTextFieldProps, 'className' | 'description'>,
PasswordFieldOwnProps {}
@@ -39,7 +39,6 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
classes,
label,
icon,
isRequired,
secondaryLabel,
placeholder,
startCollapsed,
@@ -59,7 +58,7 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
// 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);
@@ -40,6 +40,5 @@ export const SearchFieldDefinition = defineComponent<SearchFieldOwnProps>()({
label: {},
description: {},
secondaryLabel: {},
isRequired: {},
},
});
@@ -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<AriaSearchFieldProps, 'className' | 'description'>,
SearchFieldOwnProps {}
@@ -29,15 +29,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
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<HTMLDivElement, TextFieldProps>(
// 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 (
<AriaTextField
@@ -39,6 +39,5 @@ export const TextFieldDefinition = defineComponent<TextFieldOwnProps>()({
label: {},
description: {},
secondaryLabel: {},
isRequired: {},
},
});
@@ -42,12 +42,11 @@ export type TextFieldOwnProps = {
label?: FieldLabelProps['label'];
description?: FieldLabelProps['description'];
secondaryLabel?: FieldLabelProps['secondaryLabel'];
isRequired?: boolean;
};
/** @public */
export interface TextFieldProps
extends Omit<AriaTextFieldProps, 'className' | 'isRequired' | 'description'>,
extends Omit<AriaTextFieldProps, 'className' | 'description'>,
TextFieldOwnProps {
/**
* The HTML input type for the text field