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(