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

Copy link

github-actions bot commented Apr 1, 2025

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Type-related labels to choose from: [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Technical Prototype, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core, Gutenberg Plugin.
  • Labels found: New Block.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

Copy link

github-actions bot commented Apr 1, 2025

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Type-related labels to choose from: [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Technical Prototype, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core, Gutenberg Plugin.
  • Labels found: .

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

2 similar comments
Copy link

github-actions bot commented Apr 1, 2025

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Type-related labels to choose from: [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Technical Prototype, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core, Gutenberg Plugin.
  • Labels found: .

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

Copy link

github-actions bot commented Apr 1, 2025

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Type-related labels to choose from: [Type] Automated Testing, [Type] Breaking Change, [Type] Bug, [Type] Build Tooling, [Type] Code Quality, [Type] Copy, [Type] Developer Documentation, [Type] Enhancement, [Type] Experimental, [Type] Feature, [Type] New API, [Type] Task, [Type] Technical Prototype, [Type] Performance, [Type] Project Management, [Type] Regression, [Type] Security, [Type] WP Core Ticket, Backport from WordPress Core, Gutenberg Plugin.
  • Labels found: .

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

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: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: fabiankaegy <fabiankaegy@git.wordpress.org>
Co-authored-by: alexstine <alexstine@git.wordpress.org>
Co-authored-by: mikachan <mikachan@git.wordpress.org>
Co-authored-by: luisherranz <luisherranz@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.

And I thought, well I'm not actually doing any attributes in the block markup that I wouldn't be aware of so I could make this cleaner and more importantly more effecient.
@sethrubenstein
Copy link
Contributor Author

@davewhitley Merge conflicts are now resolved.

@joedolson joedolson self-requested a review July 28, 2025 20:05
@priethor priethor linked an issue Aug 4, 2025 that may be closed by this pull request
@sethrubenstein
Copy link
Contributor Author

I have an update regarding some structural changes I'm making to the iAPI implementation to make tabs more extensible for developers. Currently, I'm traversing parsed_block['innerBlocks'] to build the list of tabs and markup, but I'm moving away from that approach. Instead, I'll be relying on iAPI global state and iAPI templates.

Each tab block will add its own values, such as id and label, to the core/tabs global state. This change may not necessarily reduce code complexity; in fact, it could increase it, as I'm also introducing a render_block_data function to generate a unique ID for the tabs wrapper block. This ID will then be passed down to each tab block via block context.

The main reason for this change is to enhance extensibility. Now, the interior tab block will generate the tabs list instead of the tabs block itself. This method allows developers to envision new interior sections for tabs while simplifying interaction with the tabs list. All that's necessary for an interior section is to apply some code like this:

<?php
$tag_processor = new WP_HTML_Tag_Processor( $content );
$tab_id               = wp_unique_id( 'tab__' );
$state                 = wp_interactivity_state( 'prc-block/tabs', array() );
$state[ $block->context['tabs/id'] ][] = array(
	'id'       => $tab_id,
	'label' => $attributes['label'],
	'href'  => '#' . $tab_id,
);
wp_interactivity_state( 'prc-block/tabs', $state );

$tag_processor->set_attribute(
	'data-wp-context',
	wp_json_encode(
		array(
			'tab' => array(
				'id' => 'tab__' . $tab_id,
			),
		)
	)
);
$tag_processor->set_attribute( 'data-wp-bind--hidden', '!state.isActiveTab' );
$tag_processor->set_attribute( 'data-wp-bind--tabindex', 'state.tabIndexAttribute' );
return $tag_processor->get_updated_html();

To accomplish the following requirements: 1.) add their respective id, label, href link into core/tabs global state, and 2.) wrap their section with tab.id context, this is because the iAPI store has functionality that utilizes this pattern:

const { tab } = getContext();
const id = tab?.id;

For use in both the tabslist template and the tab sections themselves.

These changes are wrapped up in our block library at the moment, I should have time to port them into this PR after WCUS.

@mikachan
Copy link
Member

Thanks for picking this up! I'd be happy to review this when you're ready.

@davewhitley
Copy link
Contributor

davewhitley commented Aug 13, 2025

Using Gutenberg Run, I ran into an issue. Hard to to explain, but pretty soon into testing, the focus would repeatedly move away from the element that I clicked on. Notice how the insert button is flashing (that's not just the video):

CleanShot.2025-08-12.at.12.50.24-converted.mp4

So far this looks like a great and much needed addition. At a minimum though, I think there should be 2 more styles of tabs that are selectable from the sidebar. There should be button style and plain link style:

Screenshot 2025-08-13 at 9 39 35 AM

(instead of dropdown, it would say "tabs")

The tab style is actually not used very often in web design today; it's usually a plain link style or button style:

Screenshot 2025-08-13 at 9 50 54 AM Screenshot 2025-08-13 at 9 49 57 AM

It would be great to get these options out of the box along with settings to set background color, border color, etc.!

@luisherranz
Copy link
Member

I'm moving away from that approach. Instead, I'll be relying on iAPI global state and iAPI templates.

Is it your intention to do something similar to this?

$tab_id = wp_unique_id( 'tab__' );

We should look at what happens when there is a client-side navigation to make sure this block is compatible. Maybe we should use getServerState() to replace the ids, although depending on how you structure the state, it could be reset unintentionally, for example, by changing the tab that the user has open at that moment. So maybe we should try to make this id stable across renders, similar to the Query block one, which is saved as an attribute.

Anyway, just something to keep in mind as you move forward with that implementation.

@sethrubenstein
Copy link
Contributor Author

@luisherranz Good point on the unique id, I'll double check that on client side navigation. Otherwise I'll build a more reproducible id from the tab label slug. Expect another push on this PR with changes to how I'm currently handling the tabs list shortly after WCUS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Block Suggestion for a new block
Projects
None yet
Development

Successfully merging this pull request may close these issues.

New Block: Tabs
8 participants