diff --git a/packages/components/src/higher-order/with-constrained-tabbing/index.js b/packages/components/src/higher-order/with-constrained-tabbing/index.js
index 8d1574e0a20197..75745cc9d02429 100644
--- a/packages/components/src/higher-order/with-constrained-tabbing/index.js
+++ b/packages/components/src/higher-order/with-constrained-tabbing/index.js
@@ -33,6 +33,13 @@ const withConstrainedTabbing = createHigherOrderComponent(
} else if ( ! event.shiftKey && event.target === lastTabbable ) {
event.preventDefault();
firstTabbable.focus();
+ /*
+ * When pressing Tab and none of the tabbables has focus, the keydown
+ * event happens on the wrapper div: move focus on the first tabbable.
+ */
+ } else if ( ! tabbables.includes( event.target ) ) {
+ event.preventDefault();
+ firstTabbable.focus();
}
}
@@ -44,6 +51,7 @@ const withConstrainedTabbing = createHigherOrderComponent(
diff --git a/test/e2e/specs/a11y.test.js b/test/e2e/specs/a11y.test.js
index 4a15f7ccf8668e..704ae68bba49e4 100644
--- a/test/e2e/specs/a11y.test.js
+++ b/test/e2e/specs/a11y.test.js
@@ -1,10 +1,20 @@
/**
* Internal dependencies
*/
-import { newPost, pressWithModifier } from '../support/utils';
+import {
+ ACCESS_MODIFIER_KEYS,
+ newPost,
+ pressWithModifier,
+} from '../support/utils';
+
+function isCloseButtonFocused() {
+ return page.$eval( ':focus', ( focusedElement ) => {
+ return focusedElement.getAttribute( 'aria-label' ) === 'Close dialog';
+ } );
+}
describe( 'a11y', () => {
- beforeAll( async () => {
+ beforeEach( async () => {
await newPost();
} );
@@ -19,4 +29,43 @@ describe( 'a11y', () => {
expect( isFocusedToggle ).toBe( true );
} );
+
+ it( 'constrains focus to a modal when tabbing', async () => {
+ // Open help modal
+ await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' );
+
+ // Test that the Close button of the modal is focused when the
+ // latter is opened.
+ expect( await isCloseButtonFocused() ).toBe( true );
+
+ await page.keyboard.press( 'Tab' );
+
+ // Test that the Close button of the modal is focused when the
+ // latter is opened.
+ expect( await isCloseButtonFocused() ).toBe( true );
+ } );
+
+ it( 'returns focus to the first tabbable in a modal after blurring a tabbable', async () => {
+ await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' );
+
+ // Click to move focus to an element after the last tabbable within the
+ // modal.
+ await page.click( '.components-modal__content' );
+
+ await page.keyboard.press( 'Tab' );
+
+ expect( await isCloseButtonFocused() ).toBe( true );
+ } );
+
+ it( 'returns focus to the last tabbable in a modal after blurring a tabbable and tabbing in reverse direction', async () => {
+ await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' );
+
+ // Click to move focus to an element before the first tabbable within
+ // the modal.
+ await page.click( '.components-modal__header-heading' );
+
+ await pressWithModifier( 'Shift', 'Tab' );
+
+ expect( await isCloseButtonFocused() ).toBe( true );
+ } );
} );