diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js
index de482c5f059dc8..673562fde34f58 100644
--- a/packages/block-editor/src/components/iframe/index.js
+++ b/packages/block-editor/src/components/iframe/index.js
@@ -273,13 +273,16 @@ function Iframe( {
 				src={ src }
 				title={ __( 'Editor canvas' ) }
 				onKeyDown={ ( event ) => {
+					if ( props.onKeyDown ) {
+						props.onKeyDown( event );
+					}
 					// If the event originates from inside the iframe, it means
 					// it bubbled through the portal, but only with React
 					// events. We need to to bubble native events as well,
 					// though by doing so we also trigger another React event,
 					// so we need to stop the propagation of this event to avoid
 					// duplication.
-					if (
+					else if (
 						event.currentTarget.ownerDocument !==
 						event.target.ownerDocument
 					) {
diff --git a/test/e2e/specs/site-editor/navigation.spec.js b/test/e2e/specs/site-editor/navigation.spec.js
new file mode 100644
index 00000000000000..25a5b5dee59ffb
--- /dev/null
+++ b/test/e2e/specs/site-editor/navigation.spec.js
@@ -0,0 +1,119 @@
+/**
+ * WordPress dependencies
+ */
+const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
+
+test.use( {
+	editorNavigationUtils: async ( { page, pageUtils }, use ) => {
+		await use( new EditorNavigationUtils( { page, pageUtils } ) );
+	},
+} );
+
+test.describe( 'Site editor navigation', () => {
+	test.beforeAll( async ( { requestUtils } ) => {
+		await requestUtils.activateTheme( 'emptytheme' );
+	} );
+
+	test.afterAll( async ( { requestUtils } ) => {
+		await requestUtils.activateTheme( 'twentytwentyone' );
+	} );
+
+	test( 'Can use keyboard to navigate the site editor', async ( {
+		admin,
+		editorNavigationUtils,
+		page,
+		pageUtils,
+	} ) => {
+		await admin.visitSiteEditor();
+
+		// Test: Can navigate to a sidebar item and into its subnavigation frame without losing focus
+		// Go to the Pages button
+
+		await editorNavigationUtils.tabToLabel( 'Pages' );
+
+		await expect(
+			page.getByRole( 'button', { name: 'Pages' } )
+		).toBeFocused();
+		await pageUtils.pressKeys( 'Enter' );
+		// We should be in the Pages sidebar
+		await expect(
+			page.getByRole( 'button', { name: 'Back', exact: true } )
+		).toBeFocused();
+		await pageUtils.pressKeys( 'Enter' );
+		// Go back to the main navigation
+		await expect(
+			page.getByRole( 'button', { name: 'Pages' } )
+		).toBeFocused();
+
+		// Test: Can navigate into the iframe using the keyboard
+		await editorNavigationUtils.tabToLabel( 'Editor Canvas' );
+		const editorCanvasButton = page.getByRole( 'button', {
+			name: 'Editor Canvas',
+		} );
+		await expect( editorCanvasButton ).toBeFocused();
+		// Enter into the site editor frame
+		await pageUtils.pressKeys( 'Enter' );
+		// Focus should be on the iframe without the button role
+		await expect(
+			page.locator( 'iframe[name="editor-canvas"]' )
+		).toBeFocused();
+		// The button role should have been removed from the iframe.
+		await expect( editorCanvasButton ).toBeHidden();
+
+		// Test to make sure a Tab keypress works as expected.
+		// As of this writing, we are in select mode and a tab
+		// keypress will reveal the header template select mode
+		// button. This test is not documenting that we _want_
+		// that action, but checking that we are within the site
+		// editor and keypresses work as intened.
+		await pageUtils.pressKeys( 'Tab' );
+		await expect(
+			page.getByRole( 'button', {
+				name: 'Template Part Block. Row 1. header',
+			} )
+		).toBeFocused();
+
+		// Test: We can go back to the main navigation from the editor frame
+		// Move to the document toolbar
+		await pageUtils.pressKeys( 'alt+F10' );
+		// Go to the open navigation button
+		await pageUtils.pressKeys( 'shift+Tab' );
+
+		// Open the sidebar again
+		await expect(
+			page.getByRole( 'button', {
+				name: 'Open Navigation',
+				exact: true,
+			} )
+		).toBeFocused();
+		await pageUtils.pressKeys( 'Enter' );
+
+		await expect(
+			page.getByLabel( 'Go to the Dashboard' ).first()
+		).toBeFocused();
+		// We should have our editor canvas button back
+		await expect( editorCanvasButton ).toBeVisible();
+	} );
+} );
+
+class EditorNavigationUtils {
+	constructor( { page, pageUtils } ) {
+		this.page = page;
+		this.pageUtils = pageUtils;
+	}
+
+	async tabToLabel( label, times = 10 ) {
+		for ( let i = 0; i < times; i++ ) {
+			await this.pageUtils.pressKeys( 'Tab' );
+			const activeLabel = await this.page.evaluate( () => {
+				return (
+					document.activeElement.getAttribute( 'aria-label' ) ||
+					document.activeElement.textContent
+				);
+			} );
+			if ( activeLabel === label ) {
+				return;
+			}
+		}
+	}
+}