diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md
index a8cdcf82c96657..e8b0e7ea697083 100644
--- a/packages/components/src/button/README.md
+++ b/packages/components/src/button/README.md
@@ -115,6 +115,17 @@ The presence of a `href` prop determines whether an `anchor` element is rendered
Props not included in this set will be applied to the `a` or `button` element.
+#### `accessibleWhenDisabled`: `boolean`
+
+Whether to keep the button focusable when disabled.
+
+In most cases, it is recommended to set this to `true`. Disabling a control without maintaining focusability can cause accessibility issues, by hiding their presence from screen reader users, or by preventing focus from returning to a trigger element.
+
+Learn more about the [focusability of disabled controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols) in the WAI-ARIA Authoring Practices Guide.
+
+- Required: No
+- Default: `false`
+
#### `children`: `ReactNode`
The button's children.
@@ -137,6 +148,8 @@ An accessible description for the button.
Whether the button is disabled. If `true`, this will force a `button` element to be rendered, even when an `href` is given.
+In most cases, it is recommended to also set the `accessibleWhenDisabled` prop to `true`.
+
- Required: No
#### `href`: `string`
diff --git a/packages/components/src/button/index.tsx b/packages/components/src/button/index.tsx
index e2ef34f35712e7..ac495007838403 100644
--- a/packages/components/src/button/index.tsx
+++ b/packages/components/src/button/index.tsx
@@ -29,6 +29,7 @@ import { positionToPlacement } from '../popover/utils';
const disabledEventsOnDisabledButton = [ 'onMouseDown', 'onClick' ] as const;
function useDeprecatedProps( {
+ __experimentalIsFocusable,
isDefault,
isPrimary,
isSecondary,
@@ -43,7 +44,8 @@ function useDeprecatedProps( {
let computedSize = size;
let computedVariant = variant;
- const newProps: { 'aria-pressed'?: boolean } = {
+ const newProps = {
+ accessibleWhenDisabled: __experimentalIsFocusable,
// @todo Mark `isPressed` as deprecated
'aria-pressed': isPressed,
};
@@ -91,6 +93,7 @@ export function UnforwardedButton(
) {
const {
__next40pxDefaultSize,
+ accessibleWhenDisabled,
isBusy,
isDestructive,
className,
@@ -106,7 +109,6 @@ export function UnforwardedButton(
size = 'default',
text,
variant,
- __experimentalIsFocusable: isFocusable,
describedBy,
...buttonOrAnchorProps
} = useDeprecatedProps( props );
@@ -159,7 +161,7 @@ export function UnforwardedButton(
'has-icon': !! icon,
} );
- const trulyDisabled = disabled && ! isFocusable;
+ const trulyDisabled = disabled && ! accessibleWhenDisabled;
const Tag = href !== undefined && ! trulyDisabled ? 'a' : 'button';
const buttonProps: ComponentPropsWithoutRef< 'button' > =
Tag === 'button'
@@ -177,7 +179,7 @@ export function UnforwardedButton(
const disableEventProps: {
[ key: string ]: ( event: MouseEvent ) => void;
} = {};
- if ( disabled && isFocusable ) {
+ if ( disabled && accessibleWhenDisabled ) {
// In this case, the button will be disabled, but still focusable and
// perceivable by screen reader users.
buttonProps[ 'aria-disabled' ] = true;
diff --git a/packages/components/src/button/test/index.tsx b/packages/components/src/button/test/index.tsx
index 9d8980e04c9313..d13ed65eac26a3 100644
--- a/packages/components/src/button/test/index.tsx
+++ b/packages/components/src/button/test/index.tsx
@@ -241,7 +241,7 @@ describe( 'Button', () => {
} );
it( 'should add only aria-disabled attribute when disabled and isFocusable are true', () => {
- render( );
+ render( );
const button = screen.getByRole( 'button' );
expect( button ).toBeEnabled();
@@ -617,6 +617,14 @@ describe( 'Button', () => {
'mixed'
);
} );
+
+ it( 'should not break when the legacy __experimentalIsFocusable prop is passed', () => {
+ render( );
+ const button = screen.getByRole( 'button' );
+
+ expect( button ).toBeEnabled();
+ expect( button ).toHaveAttribute( 'aria-disabled' );
+ } );
} );
describe( 'static typing', () => {
@@ -632,7 +640,7 @@ describe( 'Button', () => {
{ /* @ts-expect-error */ }
{ /* @ts-expect-error - although the runtime behavior will allow this to be an anchor, this is probably a mistake. */ }
-
+
>;
} );
} );
diff --git a/packages/components/src/button/types.ts b/packages/components/src/button/types.ts
index ce6db4b3e914bc..cba06ade2d02ca 100644
--- a/packages/components/src/button/types.ts
+++ b/packages/components/src/button/types.ts
@@ -25,6 +25,19 @@ type BaseButtonProps = {
* @default false
*/
__next40pxDefaultSize?: boolean;
+ /**
+ * Whether to keep the button focusable when disabled.
+ *
+ * In most cases, it is recommended to set this to `true`. Disabling a control without maintaining focusability
+ * can cause accessibility issues, by hiding their presence from screen reader users,
+ * or by preventing focus from returning to a trigger element.
+ *
+ * Learn more about the [focusability of disabled controls](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols)
+ * in the WAI-ARIA Authoring Practices Guide.
+ *
+ * @default false
+ */
+ accessibleWhenDisabled?: boolean;
/**
* The button's children.
*/
@@ -105,28 +118,24 @@ type BaseButtonProps = {
* 'link' (the link button styles)
*/
variant?: 'primary' | 'secondary' | 'tertiary' | 'link';
- /**
- * Whether to keep the button focusable when disabled.
- *
- * @default false
- */
- __experimentalIsFocusable?: boolean;
};
type _ButtonProps = {
/**
- * Whether the button is disabled.
+ * Whether the button is disabled. If `true`, this will force a `button` element
+ * to be rendered, even when an `href` is given.
*
- * If `true`, this will force a `button` element to be rendered, even when an `href` is given.
+ * In most cases, it is recommended to also set the `accessibleWhenDisabled` prop to `true`.
*/
disabled?: boolean;
};
type AnchorProps = {
/**
- * Whether the button is disabled.
+ * Whether the button is disabled. If `true`, this will force a `button` element
+ * to be rendered, even when an `href` is given.
*
- * If `true`, this will force a `button` element to be rendered, even when an `href` is given.
+ * In most cases, it is recommended to also set the `accessibleWhenDisabled` prop to `true`.
*/
disabled?: false;
/**
@@ -140,6 +149,14 @@ type AnchorProps = {
};
export type DeprecatedButtonProps = {
+ /**
+ * Whether to keep the button focusable when disabled.
+ *
+ * @default false
+ * @deprecated Use the `accessibleWhenDisabled` prop instead.
+ * @ignore
+ */
+ __experimentalIsFocusable?: boolean;
/**
* Gives the button a default style.
*