From 4d082ab1aa53368471ac84ab3803cc94cf1cc293 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Thu, 12 Feb 2026 16:42:39 +0000 Subject: [PATCH 1/5] Remove outline CSS --- packages/block-library/src/tab/style.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/block-library/src/tab/style.scss b/packages/block-library/src/tab/style.scss index 6e57fc896fe837..699a2d89f08c5b 100644 --- a/packages/block-library/src/tab/style.scss +++ b/packages/block-library/src/tab/style.scss @@ -15,9 +15,6 @@ &:empty { display: none !important; } - &:not(.wp-block):focus { - outline: none; - } } .wp-block-tab.wp-block.has-background, From 8e3467b29b1068b836308d4b523880561a4d863a Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Thu, 12 Feb 2026 16:51:02 +0000 Subject: [PATCH 2/5] Add tabPanelTabIndexAttribute state --- packages/block-library/src/tab/index.php | 2 +- packages/block-library/src/tabs/view.js | 40 +++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/tab/index.php b/packages/block-library/src/tab/index.php index ca5112449118cd..965332728cdeff 100644 --- a/packages/block-library/src/tab/index.php +++ b/packages/block-library/src/tab/index.php @@ -49,7 +49,7 @@ function block_core_tab_render( array $attributes, string $content ): string { $tag_processor->set_attribute( 'role', 'tabpanel' ); $tag_processor->set_attribute( 'aria-labelledby', 'tab__' . $tab_id ); $tag_processor->set_attribute( 'data-wp-bind--hidden', '!state.isActiveTab' ); - $tag_processor->set_attribute( 'data-wp-bind--tabindex', 'state.tabIndexAttribute' ); + $tag_processor->set_attribute( 'data-wp-bind--tabindex', 'state.tabPanelTabIndexAttribute' ); return (string) $tag_processor->get_updated_html(); } diff --git a/packages/block-library/src/tabs/view.js b/packages/block-library/src/tabs/view.js index db077a4eb5d509..135164ae2dc91e 100644 --- a/packages/block-library/src/tabs/view.js +++ b/packages/block-library/src/tabs/view.js @@ -86,7 +86,7 @@ const { actions: privateActions, state: privateState } = store( return activeTabIndex === tabIndex; }, /** - * The value of the tabindex attribute. + * The value of the tabindex attribute for tab buttons. * Only the active tab should be in the tab sequence. * * @type {number} @@ -94,6 +94,44 @@ const { actions: privateActions, state: privateState } = store( get tabIndexAttribute() { return privateState.isActiveTab ? 0 : -1; }, + /** + * The value of the tabindex attribute for tab panels. + * Returns 0 if the panel contains no focusable elements, + * -1 if it does (to allow direct tabbing to content). + * + * Guidelines: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ + * + * @type {number} + */ + get tabPanelTabIndexAttribute() { + const { ref } = getElement(); + + if ( ! ref ) { + return -1; + } + + // Check if panel contains any focusable elements + const focusableSelector = [ + 'a[href]', + 'button:not([disabled])', + 'input:not([disabled])', + 'select:not([disabled])', + 'textarea:not([disabled])', + '[tabindex]:not([tabindex="-1"])', + 'audio[controls]', + 'video[controls]', + '[contenteditable]:not([contenteditable="false"])', + 'details > summary:first-of-type', + 'details', + ].join( ', ' ); + + const hasFocusableContent = + ref.querySelector( focusableSelector ) !== null; + + // If panel has focusable content, use -1 + // If panel has no focusable content, use 0 + return hasFocusableContent ? -1 : 0; + }, }, actions: { /** From 9399190225be1908168b69b0a7fbf3f55b6d276f Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Thu, 12 Feb 2026 17:04:41 +0000 Subject: [PATCH 3/5] Fix ref logic --- packages/block-library/src/tabs/view.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/tabs/view.js b/packages/block-library/src/tabs/view.js index 135164ae2dc91e..c64ec992895983 100644 --- a/packages/block-library/src/tabs/view.js +++ b/packages/block-library/src/tabs/view.js @@ -104,9 +104,16 @@ const { actions: privateActions, state: privateState } = store( * @type {number} */ get tabPanelTabIndexAttribute() { - const { ref } = getElement(); + const { attributes } = getElement(); + const panelId = attributes?.id; + + if ( ! panelId ) { + return -1; + } - if ( ! ref ) { + // Get the DOM element + const panelElement = document.getElementById( panelId ); + if ( ! panelElement ) { return -1; } @@ -117,16 +124,16 @@ const { actions: privateActions, state: privateState } = store( 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', - '[tabindex]:not([tabindex="-1"])', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details > summary:first-of-type', 'details', + 'iframe', ].join( ', ' ); const hasFocusableContent = - ref.querySelector( focusableSelector ) !== null; + panelElement.querySelector( focusableSelector ) !== null; // If panel has focusable content, use -1 // If panel has no focusable content, use 0 From 0bf6b1fa92c80a3d7beec8a1064eaefea9384a73 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Fri, 13 Feb 2026 13:29:53 +0000 Subject: [PATCH 4/5] Simplify tabPanelTabIndexAttribute --- packages/block-library/src/tabs/view.js | 37 +------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/packages/block-library/src/tabs/view.js b/packages/block-library/src/tabs/view.js index c64ec992895983..e81b5acd0219be 100644 --- a/packages/block-library/src/tabs/view.js +++ b/packages/block-library/src/tabs/view.js @@ -96,48 +96,13 @@ const { actions: privateActions, state: privateState } = store( }, /** * The value of the tabindex attribute for tab panels. - * Returns 0 if the panel contains no focusable elements, - * -1 if it does (to allow direct tabbing to content). * * Guidelines: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ * * @type {number} */ get tabPanelTabIndexAttribute() { - const { attributes } = getElement(); - const panelId = attributes?.id; - - if ( ! panelId ) { - return -1; - } - - // Get the DOM element - const panelElement = document.getElementById( panelId ); - if ( ! panelElement ) { - return -1; - } - - // Check if panel contains any focusable elements - const focusableSelector = [ - 'a[href]', - 'button:not([disabled])', - 'input:not([disabled])', - 'select:not([disabled])', - 'textarea:not([disabled])', - 'audio[controls]', - 'video[controls]', - '[contenteditable]:not([contenteditable="false"])', - 'details > summary:first-of-type', - 'details', - 'iframe', - ].join( ', ' ); - - const hasFocusableContent = - panelElement.querySelector( focusableSelector ) !== null; - - // If panel has focusable content, use -1 - // If panel has no focusable content, use 0 - return hasFocusableContent ? -1 : 0; + return 0; }, }, actions: { From 031058843c341913c7f1f65599c955d5ee2d20ed Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Fri, 13 Feb 2026 15:15:41 +0000 Subject: [PATCH 5/5] Remove tabPanelTabIndexAttribute --- packages/block-library/src/tab/index.php | 2 +- packages/block-library/src/tabs/view.js | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/block-library/src/tab/index.php b/packages/block-library/src/tab/index.php index 965332728cdeff..2b4e6574a6f615 100644 --- a/packages/block-library/src/tab/index.php +++ b/packages/block-library/src/tab/index.php @@ -49,7 +49,7 @@ function block_core_tab_render( array $attributes, string $content ): string { $tag_processor->set_attribute( 'role', 'tabpanel' ); $tag_processor->set_attribute( 'aria-labelledby', 'tab__' . $tab_id ); $tag_processor->set_attribute( 'data-wp-bind--hidden', '!state.isActiveTab' ); - $tag_processor->set_attribute( 'data-wp-bind--tabindex', 'state.tabPanelTabIndexAttribute' ); + $tag_processor->set_attribute( 'tabindex', 0 ); return (string) $tag_processor->get_updated_html(); } diff --git a/packages/block-library/src/tabs/view.js b/packages/block-library/src/tabs/view.js index e81b5acd0219be..4aba2d0b7b36a8 100644 --- a/packages/block-library/src/tabs/view.js +++ b/packages/block-library/src/tabs/view.js @@ -94,16 +94,6 @@ const { actions: privateActions, state: privateState } = store( get tabIndexAttribute() { return privateState.isActiveTab ? 0 : -1; }, - /** - * The value of the tabindex attribute for tab panels. - * - * Guidelines: https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ - * - * @type {number} - */ - get tabPanelTabIndexAttribute() { - return 0; - }, }, actions: { /**