Skip to content

Conversation

@levinbaria
Copy link
Contributor

What?

Closes: #71776

This PR changes the Accordion blocks so the heading level is controlled from the Accordion Group instead of from each individual Accordion Header.

  • Adds a headingLevel attribute to the Accordion Group.

  • Removes the heading-level control from individual Accordion Header toolbars in the editor UI.

  • Accordion Header rendering now uses the group headingLevel so all headers within the same group have the same semantic level in output.

  • This keeps the UI simpler (one place to choose the heading level) and fixes inconsistent heading levels and accessibility issues

Why?

Currently each Accordion Header exposes its own heading-level control in the toolbar. That causes two problems:

  • Inconsistent semantic structure
  • Toolbar redundancy and UX clutter

How?

  • Adds a headingLevel attribute to the Accordion Group and expose it in the block settings/inspector.

  • Remove the per-header heading-level control from each Accordion Header in the editor UI.

  • Pass the group headingLevel to child header components so they render the correct both in the editor preview and on save.

Testing Instructions

  1. Insert an Accordion block.
  2. Add multiple Accordion Header items within the group and fill with content.
  3. Use the Accordion Group setting to change the heading level (e.g., H3 → H4).
  4. Observe editor preview: all Accordion Headers should now show the selected heading level.
  5. Inspect the front-end (Preview) markup: each header should render with the chosen tag (h3 or h4).

Testing Instructions for Keyboard

Screenshots or screencast

Before After

@github-actions
Copy link

github-actions bot commented Sep 25, 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.

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

Co-authored-by: levinbaria <levinbaria@git.wordpress.org>
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>
Co-authored-by: joedolson <joedolson@git.wordpress.org>

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

@Mamaduka Mamaduka added [Type] Bug An existing feature does not function as intended [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Block] Accordion Affects the Accordion Block labels Sep 25, 2025
@levinbaria levinbaria force-pushed the update/header-synchronization branch from ffc9308 to bc1d388 Compare September 26, 2025 15:36
@t-hamano
Copy link
Contributor

This PR works as one approach, but I'm wondering if there's a way to sync headings other than using effects.

Another approach I'm thinking of is the following, does that make sense? In other words, the markup saved is just temporary, and the heading levels are actually set server-side:

export default function save( { attributes } ) {
	const { title } = attributes;

	const blockProps = useBlockProps.save();

	return (
		// The heading level is set in the Accordion block, but the save function cannot reference the context.
		// Therefore, the heading level is hard-coded here. The actual heading level will be changed server-side.
		<h3 { ...blockProps }>
			<button className="wp-block-accordion-header__toggle">
				<RichText.Content value={ title } />
			</button>
		</h3>
	);
}

Can anyone think of any other approaches?

@t-hamano t-hamano added the Needs Technical Feedback Needs testing from a developer perspective. label Sep 27, 2025
@levinbaria levinbaria force-pushed the update/header-synchronization branch from bc1d388 to 2d03be6 Compare September 27, 2025 07:05
@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

I would like to ask contributors who are familiar with block development for help if they have any ideas.

@aaronrobertshaw @andrewserong @ntsekouras @scruffian @ramonjd

We are trying to synchronize the heading levels of the Accordion Header block within the Accordion block:

  • Accordion
    • Accordion Content
      • Accordion Header > want to synchronize the heading levels of this block!
      • Accordion Panel
    • Accordion Content
      • Accordion Header > want to synchronize the heading levels of this block!
      • Accordion Panel

Even if we add a level control to the Accordion block and provide it as context, we cannot reference the context in the save function. Therefore, the only way we can think of to synchronize the levels frequently is via useEffect. Do you have any ideas?

@ramonjd
Copy link
Member

ramonjd commented Oct 1, 2025

Even if we add a level control to the Accordion block and provide it as context, we cannot reference the context in the save function. Therefore, the only way we can think of to synchronize the levels frequently is via useEffect. Do you have any ideas?

In the Accordian block, you could use updateBlockAttributes on the innerblocks. Columns block already does this for some attributes.

Since the Accordian header already has a level attribute, we can create a callback that updates it via updateBlockAttributes - no useEffect required.

Here's a working example:

Diff
diff --git a/packages/block-library/src/accordion-header/block.json b/packages/block-library/src/accordion-header/block.json
index 7181ef80b3..4d3baec8b7 100644
--- a/packages/block-library/src/accordion-header/block.json
+++ b/packages/block-library/src/accordion-header/block.json
@@ -9,8 +9,7 @@
 	"parent": [ "core/accordion-content" ],
 	"usesContext": [
 		"core/accordion-icon-position",
-		"core/accordion-show-icon",
-		"core/accordion-heading-level"
+		"core/accordion-show-icon"
 	],
 	"supports": {
 		"anchor": true,
diff --git a/packages/block-library/src/accordion-header/edit.js b/packages/block-library/src/accordion-header/edit.js
index 545af878aa..b6cf869266 100644
--- a/packages/block-library/src/accordion-header/edit.js
+++ b/packages/block-library/src/accordion-header/edit.js
@@ -10,13 +10,12 @@ import {
 } from '@wordpress/block-editor';
 
 export default function Edit( { attributes, setAttributes, context } ) {
-	const { title } = attributes;
+	const { title, level } = attributes;
 	const {
 		'core/accordion-icon-position': iconPosition,
 		'core/accordion-show-icon': showIcon,
-		'core/accordion-heading-level': headingLevel,
 	} = context;
-	const TagName = 'h' + headingLevel;
+	const TagName = 'h' + ( level || 3 );
 
 	// Set icon attributes.
 	useEffect( () => {
@@ -28,13 +27,6 @@ export default function Edit( { attributes, setAttributes, context } ) {
 		}
 	}, [ iconPosition, showIcon, setAttributes ] );
 
-	// Sync heading level from context.
-	useEffect( () => {
-		if ( headingLevel !== undefined && headingLevel !== attributes.level ) {
-			setAttributes( { level: headingLevel } );
-		}
-	}, [ headingLevel, attributes.level, setAttributes ] );
-
 	const blockProps = useBlockProps();
 	const spacingProps = useSpacingProps( attributes );
 
diff --git a/packages/block-library/src/accordion/block.json b/packages/block-library/src/accordion/block.json
index 37136a9742..89294106cd 100644
--- a/packages/block-library/src/accordion/block.json
+++ b/packages/block-library/src/accordion/block.json
@@ -78,8 +78,7 @@
 	},
 	"providesContext": {
 		"core/accordion-icon-position": "iconPosition",
-		"core/accordion-show-icon": "showIcon",
-		"core/accordion-heading-level": "headingLevel"
+		"core/accordion-show-icon": "showIcon"
 	},
 	"allowedBlocks": [ "core/accordion-content" ],
 	"textdomain": "default",
diff --git a/packages/block-library/src/accordion/edit.js b/packages/block-library/src/accordion/edit.js
index 38a74aeb77..d93a131244 100644
--- a/packages/block-library/src/accordion/edit.js
+++ b/packages/block-library/src/accordion/edit.js
@@ -19,7 +19,7 @@ import {
 	ToolbarButton,
 	ToolbarGroup,
 } from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
+import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
 import { createBlock } from '@wordpress/blocks';
 
 /**
@@ -45,6 +45,9 @@ export default function Edit( {
 	isSelected: isSingleSelected,
 } ) {
 	const blockProps = useBlockProps();
+	const registry = useRegistry();
+	const { getBlockOrder } = useSelect( blockEditorStore );
+	const { updateBlockAttributes } = useDispatch( blockEditorStore );
 	const dropdownMenuProps = useToolsPanelDropdownMenuProps();
 	const { insertBlock } = useDispatch( blockEditorStore );
 
@@ -60,6 +63,30 @@ export default function Edit( {
 		insertBlock( newAccordionContent, undefined, clientId );
 	};
 
+	/**
+	 * Update all child Accordion Header blocks with a new heading level
+	 * based on the accordion group setting.
+	 * @param {number} newHeadingLevel The new heading level to set
+	 */
+	function updateHeadingLevel( newHeadingLevel ) {
+		const innerBlockClientIds = getBlockOrder( clientId );
+
+		// Get all accordion-header blocks from all accordion-content blocks
+		const accordionHeaderClientIds = [];
+		innerBlockClientIds.forEach( ( contentClientId ) => {
+			const headerClientIds = getBlockOrder( contentClientId );
+			accordionHeaderClientIds.push( ...headerClientIds );
+		} );
+
+		// Update own and child block heading levels
+		registry.batch( () => {
+			setAttributes( { headingLevel: newHeadingLevel } );
+			updateBlockAttributes( accordionHeaderClientIds, {
+				level: newHeadingLevel,
+			} );
+		} );
+	}
+
 	return (
 		<>
 			{ isSingleSelected && (
@@ -69,9 +96,7 @@ export default function Edit( {
 							<HeadingLevelDropdown
 								value={ headingLevel }
 								options={ levelOptions }
-								onChange={ ( newLevel ) =>
-									setAttributes( { headingLevel: newLevel } )
-								}
+								onChange={ updateHeadingLevel }
 							/>
 						</ToolbarGroup>
 					</BlockControls>

Kapture.2025-10-01.at.12.11.08.mp4

Does that help?

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

@ramonjd Thanks for the feedback! The tricky part is that the heading levels have to be synchronized even when a new Accordion Content block is inserted 😅

@ramonjd
Copy link
Member

ramonjd commented Oct 1, 2025

The tricky part is that the heading levels have to be synchronized even when a new Accordion Content block is inserted

Sorry, I'm not quite sure what the requirements are then. What is the example use case?

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

What is the example use case?

After applying your patch to this PR, please do the following:

  • Insert an Accordion block
  • Change heading level
  • Add new Accordion Content block
  • The new Accordion Content will have the default heading level of 3
b87cee75ece9d8df9461b2afa25b3f7e.mp4

@ramonjd
Copy link
Member

ramonjd commented Oct 1, 2025

The new Accordion Content will have the default heading level of 3

Ah, thanks for explaining it 🤦🏻 I get it now 😄

I guess you could use a combination of approaches:

  • context for the initial value
  • for updates use updateBlockAttributes

But then you might have to ditch the default value "default": 3 in the block.json 🤷🏻

Not sure, sorry!

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

Add new Accordion Content block
The new Accordion Content will have the default heading level of 3

Wait, when adding a new Accordion Content block, can't we simply reference the context and insert a block with the appropriate heading?

If so, it might be best to add this approach to @ramonjd's patch.

@andrewserong
Copy link
Contributor

Wait, when adding a new Accordion Content block, can't we simply reference the context and insert a block with the appropriate heading?

Yeah, using context for new blocks plus Ramon's diff for updating existing blocks sounds like a good approach, as it means no useEffect and we're only iterating over the children to make updates when a user initiates the change. Worth a try!

@ramonjd
Copy link
Member

ramonjd commented Oct 1, 2025

Wait, when adding a new Accordion Content block, can't we simply reference the context and insert a block with the appropriate heading?

Yeah, that's what I was getting at. 👍🏻

One of my sentences was cut off in the above comment (now edited), I wanted to write:

I guess you could use a combination of approaches:

context for the initial value
for updates use updateBlockAttributes

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

Thanks for the feedback, everyone!

@levinbaria, could you resolve the conflicts and implement the suggested approach?

@levinbaria levinbaria force-pushed the update/header-synchronization branch from 2d03be6 to 3381b21 Compare October 1, 2025 11:15
Copy link
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

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

LGTM! Everything seems to be working fine 👍

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

Note: The failing Create Block Checks aren't related to this PR. That's the GitHub problem itself. See: https://wordpress.slack.com/archives/C02QB2JS7/p1759320105871839

Let's wait a bit longer and if it doesn't fix the issue, merge this PR.

@t-hamano
Copy link
Contributor

t-hamano commented Oct 1, 2025

@levinbaria sorry, it looks like there's a conflict again 😅 could you run npm run docs:build locally, then commit and push?

Update: never mind, I was able to resolve the conflict on GitHub.

@levinbaria
Copy link
Contributor Author

Thanks @t-hamano for taking care of that, appreciate it.

@t-hamano t-hamano merged commit f180f80 into WordPress:trunk Oct 1, 2025
74 of 76 checks passed
@github-actions github-actions bot added this to the Gutenberg 21.8 milestone Oct 1, 2025
@ramonjd
Copy link
Member

ramonjd commented Oct 1, 2025

Nice work, folks!

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

Labels

[Block] Accordion Affects the Accordion Block [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). Needs Technical Feedback Needs testing from a developer perspective. [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accordion Header: Synchronize heading levels

5 participants