Skip to content

Conversation

@sethrubenstein
Copy link
Contributor

@sethrubenstein sethrubenstein commented Apr 1, 2025

What?

This is Pew Research Center's approach to Tabs in the block editor and would fulfill much of the needs requested in #34079.

This is a new PR picking up where I left off on #69256

This builds upon work started by @creativecoder in #63689 and expands on making this concept even more extensible for developers.

The major changes from this original concept are the following:

  • An overhaul of tab editing has been implemented. A Slotfill (thanks @fabiankaegy for the idea) is now used to manage the tabs list and individual tab items. This enhances the user experience in two key ways:
      1. Selecting a tab now highlights the entire block, making it easier to start editing.
      1. Perhaps more importantly, we're not monitoring changes to innerblocks content to manage the tabs list, which caused significant performance issues when there were more than 10 tabs. Additionally, with the new slotfill selection method, we no longer render the innerblocks if the tab is not selected. This also improves the overall editing performance of the block.
  • Some attribute changes were made, notably the removal of tabIndex. We now add the tab index to each tab during render time. This change allows for greater programmatic usage of tabs and simplifies the process of copying and pasting new tab panes in the editor.
  • Structural changes to the core/tab block will allow it to degrade more gracefully by enabling the core/tabs block to control all Interactivity API adoption. If a core/tab block is displayed outside of core/tabs, it should simply show the tab content without any issues or modifications.
  • The addition of various tab state colors as attributes and color controls enhances the user interface. Now, there are color controls and simple styles to support tab hover, tab active, tab background, text, and more.
  • The addition of "deep linking" support allows for URL activation of a specific tab for content sharing. By passing a desired tab's anchor id into the address bar the tabs block will auto open to that tab on page load and scroll so it is in the viewport.

Why?

As the block editor and the concept of "full site editing" matures, I'm a strong believer that some basic ui concepts and paradigms should be available in the block library. Tabs is one such basic ui concept, dialog/modals are another.

How?

Testing Instructions

  1. Open a post
  2. Add a tabs block
  3. To start, two sample tabs are generated; click the plus icon in the tabs list to create a new tab
  4. Add content
  5. Preview and ensure tabs are selectable.

Testing Instructions for Keyboard

On the frontend, simply tab and hit enter to navigate through tabs. In the editor, unfortunately due to the Slotfill nature of selecting and activating tabs this is currently not possible. (This is now possible by moving the logic into the tab link itself see #69789 (comment))

Screenshots or screencast

CleanShot.2025-04-01.at.12.33.39.mp4

@github-actions
Copy link

github-actions bot commented Apr 1, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @davewhitley, @deborah86.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: davewhitley, deborah86.

Co-authored-by: sethrubenstein <smrubenstein@git.wordpress.org>
Co-authored-by: Infinite-Null <ankitkumarshah@git.wordpress.org>
Co-authored-by: mikachan <mikachan@git.wordpress.org>
Co-authored-by: sirreal <jonsurrell@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: fabiankaegy <fabiankaegy@git.wordpress.org>
Co-authored-by: alexstine <alexstine@git.wordpress.org>
Co-authored-by: luisherranz <luisherranz@git.wordpress.org>
Co-authored-by: kjnanda <krupajnanda@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: joedolson <joedolson@git.wordpress.org>
Co-authored-by: mtias <matveb@git.wordpress.org>
Co-authored-by: jarekmorawski <jarekmorawski@git.wordpress.org>
Co-authored-by: Jabe64 <jabe@git.wordpress.org>
Co-authored-by: paaljoachim <paaljoachim@git.wordpress.org>
Co-authored-by: jameskoster <jameskoster@git.wordpress.org>
Co-authored-by: gziolo <gziolo@git.wordpress.org>
Co-authored-by: richtabor <richtabor@git.wordpress.org>
Co-authored-by: creativecoder <grantmkin@git.wordpress.org>
Co-authored-by: hanneslsm <hanneslsm@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@sethrubenstein sethrubenstein mentioned this pull request Apr 1, 2025
@sethrubenstein
Copy link
Contributor Author

Alright alright alright, all tests (minus 2 that I'm pretty sure are unrelated to my PR) have passed 😁 and I am ready for people to start reviewing and offer feedback on the new core/tabs and core/tab blocks.

@Mamaduka Mamaduka added the New Block Suggestion for a new block label Apr 1, 2025
@Mamaduka
Copy link
Member

Mamaduka commented Apr 3, 2025

Thanks for contributing, @sethrubenstein!

An overhaul of tab editing has been implemented. A Slotfill (thanks @fabiankaegy for the idea) is now used to manage the tabs list and individual tab items.

What was an initial version of this? My worry here is that SlotFills are public APIs by default, so anyone who knows their name (and can read source code) can add anything to them. This can break the tab block or prevent future iterations. Similar public APIs limit our ability to make significant changes to the UI.

@fabiankaegy
Copy link
Member

@Mamaduka you can view my original comment here: #63689 (comment)

Are those slot fills when implemented that way accessible to anyone? I though they had to actually go through exported components in order to be connected properly 🤔

@Mamaduka
Copy link
Member

Mamaduka commented Apr 3, 2025

Are those slot fills when implemented that way accessible to anyone?

Technically, yes. If you know the name key, you can inject your components by using the Fill component. The core uses Symbol for some private/experimental SlotFills to avoid leaking the APIs.

Example:

Platform.OS === 'web' ? Symbol( 'ViewMoreMenuGroup' ) : 'ViewMoreMenuGroup'

I've yet to test this; I was just curious about design decisions. I think it's an interesting challenge, and I'm looking forward to seeing the final result :)

P.S. This also reminded me of the Tabs API designs by UI libraries. Example: https://ariakit.org/components/tab.

@sethrubenstein
Copy link
Contributor Author

Thanks for contributing, @sethrubenstein!

An overhaul of tab editing has been implemented. A Slotfill (thanks @fabiankaegy for the idea) is now used to manage the tabs list and individual tab items.

What was an initial version of this? My worry here is that SlotFills are public APIs by default, so anyone who knows their name (and can read source code) can add anything to them. This can break the tab block or prevent future iterations. Similar public APIs limit our ability to make significant changes to the UI.

That is an interesting point, I hadn't thought of that. I'll try the Symbol method to see if I can get that to work well and get back to you all later in the week.

@sethrubenstein
Copy link
Contributor Author

That SlotFill key scoping solution worked without issue @Mamaduka, I'll keep that one in mind whenever I need to make a "private" SlotFill.

Also, updated the icons so they're in @wordpress/icons proper @Infinite-Null

@alexstine
Copy link
Contributor

@sethrubenstein Not really involved these days in WordPress but every now and then, I glance over the PRs.

In the editor, unfortunately due to the Slotfill nature of selecting and activating tabs this is currently not possible.

If this PR is starting off by admitting that keyboard only users will not have access in the editor, maybe this type of UI doesn't belong in core. Just my two sense without testing everything. Maybe you've thought of a way around this.

@sethrubenstein
Copy link
Contributor Author

sethrubenstein commented Apr 14, 2025

@alexstine Yeah this is something I definitely would like some feedback and testing on. At first I thought my keyboard handlers on the parent block were the only way to go, and they were not firing because of losing focus when I tried to navigate between tabs so I pulled them. I just went back in and changed out the <a/> tab link elements for <button/> elements and moved my handlers for when the user select’s the tab there and this is surprisingly working pretty well.

I’m debating changing the frontend elements to be buttons instead of a tags as well if it were not for the href signal? Anyone else have another suggestions on keyboard handlers that should be present that currently are not?

@alexstine
Copy link
Contributor

Seems like the tabs on the front end really don't work with screen readers. Needs to be adjusted to follow spec.

https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/tab_role#example

Back-end more or less works now but I don't have time to inspect the code at the moment to see if improvements can be made.

FYI @afercia @joedolson

@sethrubenstein
Copy link
Contributor Author

@alexstine Thanks, I'll get that changed sometime tomorrow.

@sethrubenstein
Copy link
Contributor Author

I had the wrong aria-role and some unneeded structure, fixed.

@alexstine
Copy link
Contributor

@sethrubenstein This is testing much better now. Nice.

@davewhitley
Copy link
Contributor

@sethrubenstein Hi! I'm excited about your PR. I'd be glad to review the design of this, once the conflicts are resolved.

@sethrubenstein
Copy link
Contributor Author

@davewhitley Thanks! I'll get this merge conflicts resolved on Monday and ping you.

@sethrubenstein
Copy link
Contributor Author

Huzzah. Okay @mikachan your requested updates are in.

  • I made some changes around initial block insertion behavior to account for focusing the first tab that I think is a little cleaner experience.
  • I also updated the iAPI store to match how I'm handling things for core/dialog 3rd party extensibility per @luisherranz .

I think the last main focus area for me is cleaning up keyboard navigation in the editor context.

@sethrubenstein
Copy link
Contributor Author

Updated video of initial block experience:

CleanShot.2025-10-23.at.13.58.28.mp4

@sethrubenstein
Copy link
Contributor Author

Hmmm, another update. I realized there was no really good way to reflect changing order of tabs with the slotfill method. So a slight refactor. Now the tabslist is constructed more deliberate component and reflects actual real state of the tabs innerblocks. I also was having a look at how Accordion has progressed. I think it makes sense in how that block is handling adding a new set of it's interior blocks to remove the add tab icon in the tabs list and keep with the "add" block toolbar button. It's out of the way and lets the tabs content more reflect what you'll see on the frontend. The same preferred focus logic works here as well, clicking on an item in the tabslist focuses that entire block.

CleanShot.2025-10-23.at.15.08.04.mp4

Copy link
Member

@mikachan mikachan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing all the recent feedback, @sethrubenstein 🙇 This block is working really well for me. I think we should bring it in, and we can make any further changes iteratively.

@priethor priethor added the [Type] Feature New feature to highlight in changelogs. label Oct 29, 2025
@mikachan mikachan merged commit 74a2b27 into WordPress:trunk Oct 30, 2025
37 checks passed
@github-actions github-actions bot added this to the Gutenberg 22.1 milestone Oct 30, 2025
Copy link
Member

@luisherranz luisherranz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

A few small comments about the public store:

* @type {number|null}
*/
get tabIndex() {
return createReadOnlyProxy( privateState.tabIndex );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a proxy if it's not an array or an object.

Suggested change
return createReadOnlyProxy( privateState.tabIndex );
return privateState.tabIndex;

* @type {boolean}
*/
get isActiveTab() {
return createReadOnlyProxy( privateState.isActiveTab );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Suggested change
return createReadOnlyProxy( privateState.isActiveTab );
return privateState.isActiveTab;

* @type {Array}
*/
get tabsList() {
return createReadOnlyProxy( privateState.tabsList );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For arrays, we should also prevent the use of functions that mutate the original array, like push.

*
* @type {boolean}
*/
get isActiveTab() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this getter could also be exposed publicly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs Accessibility Feedback Need input from accessibility Needs Design Feedback Needs general design feedback. New Block Suggestion for a new block [Type] Feature New feature to highlight in changelogs.

Projects

Development

Successfully merging this pull request may close these issues.

New Block: Tabs