In Part 9 of Building a Custom Block, we’ll add a custom controls to the block toolbar so users can toggle the dismissible state or choose a block style directly from the editor canvas.
In Part 8, we restricted which blocks can be inserted inside our Notice block and explored template locking to enforce a consistent inner block structure.
Right now, the only way to toggle the isDismissible attribute is through the Inspector Controls panel in the sidebar. That works fine, but it means opening (or switching to) the sidebar every time you want to change it. I’ve found that it can help to add an option in the block toolbar and the sidebar for some attributes.
Why? Because some people are more inclined to search in the toolbar and some are more likely to search in the sidebar. If you make the control available in either place, you can further reduce friction for your users and let them work how they want to work. Which is always a nice thing to account for.
In this part, we’ll add a custom toolbar button that lets users toggle isDismissible on and off directly from the block toolbar.
Toolbar vs. Sidebar: When to Use Which
Before we jump into the code, it’s worth thinking about when a control belongs in the toolbar, the sidebar, or should display in both places.
The block toolbar is best for controls that are used frequently, are simple (toggles, dropdowns with a few options), and benefit from being close to the content. Think of it as the “quick actions” area. Things like text alignment, bold/italic, and link insertion all live here for a reason.
The sidebar (Inspector Controls) is better for controls that are more detailed, less frequently toggled, or need more space to explain what they do. Settings like color overrides, spacing adjustments, or anything with a help description are good sidebar candidates.
For isDismissible, both the sidebar and toolbar work. It’s a simple boolean toggle (true/false) that has a descriptive label that isn’t too long. We can use the same attribute and just create some more UI that sets that attribute and place it in the toolbar.
This is an established pattern found in some core blocks and as I noted above, I think it can be nice when done for the right reasons. The key is to think carefully about your block and it’s attributes and decide if it makes sense for the attribute in question.
The BlockControls Component
WordPress provides a component called BlockControls specifically for adding items to the block toolbar. It works similarly to InspectorControls in that you render it as a sibling to your block’s content and WordPress handles placing it in the right spot.
Here’s the basic structure:
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={ someIcon }
label={ __( 'Label', 'lwpd' ) }
onClick={ handleClick }
/>
</ToolbarGroup>
</BlockControls>BlockControls is the container that tells WordPress “put this in the toolbar.” This is a “slot” component similar to the InspectorControls component we discussed in Part 3. The children of the BlockControls Element gets added to the BlockToolbar in the editor.
Inside it, you use ToolbarGroup to create a visually grouped set of controls, and ToolbarButton for the individual buttons. If you’ve ever used a ToolbarGroup in a non-block context, this will feel familiar.
Adding the Dismissible Toggle
Let’s update our edit.js to include a toolbar button for the dismissible toggle. We need to import a few new things and add the BlockControls component to our return statement.
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
BlockControls,
} from '@wordpress/block-editor';
import {
PanelBody,
ToggleControl,
Icon,
ToolbarGroup,
ToolbarButton,
} from '@wordpress/components';
import { close } from '@wordpress/icons';
import { useId } from '@wordpress/element';
import './editor.scss';
export default function Edit( { attributes, setAttributes } ) {
const { isDismissible, anchor } = attributes;
const blockProps = useBlockProps( {
'data-dismissible': isDismissible || undefined,
} );
const innerBlocksProps = useInnerBlocksProps({
className: 'wp-block-lwpd-notice__children'
});
const fallbackId = 'lwpd-notice' + useId();
return (
<>
<div { ...blockProps }>
{ isDismissible && (
<button
className="wp-block-lwpd-notice__dismiss"
type="button"
aria-label={ __( 'Dismiss Notice', 'lwpd' ) }
>
<Icon icon={ close } size="24" />
</button>
) }
<div { ...innerBlocksProps } />
</div>
<InspectorControls>
<PanelBody title={ __( 'Settings', 'lwpd' ) }>
<ToggleControl
label={ __( 'Dismissible', 'lwpd' ) }
help={
isDismissible
? __( 'Users can dismiss this notice.', 'lwpd' )
: __( 'Notice will always be visible.', 'lwpd' )
}
checked={ isDismissible }
onChange={ ( value ) => {
const updates = { isDismissible: value };
if ( value && ! anchor ) {
updates.anchor = fallbackId;
}
setAttributes( updates );
} }
/>
</PanelBody>
</InspectorControls>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={close}
label={
isDismissible
? __('Remove dismiss button', 'lwpd')
: __('Add dismiss button', 'lwpd')
}
isPressed={isDismissible}
onClick={() => {
const updates = { isDismissible: !isDismissible };
if (!isDismissible && !anchor) {
updates.anchor = fallbackId;
}
setAttributes(updates);
}}
/>
</ToolbarGroup>
</BlockControls>
</>
);
}
Let’s walk through what changed.
First, we imported BlockControls from @wordpress/block-editor alongside the existing InspectorControls. We also imported ToolbarGroup and ToolbarButton from @wordpress/components.
Then we added a BlockControls section before the InspectorControls. Inside it, there’s a single ToolbarGroup containing one ToolbarButton. The button uses the same close icon we already import for the dismiss button itself, which creates a nice visual connection between the toolbar control and what it does.
The key props on ToolbarButton are:
icon: The icon to display. We’re reusing thecloseicon from@wordpress/icons.label: The accessible label (and tooltip text). We’re using a dynamic label that changes based on the current state so the user always knows what clicking the button will do.isPressed: Whentrue, the button renders in its “active” or “pressed” state. This gives users a clear visual indicator that the dismissible option is currently enabled.onClick: The handler that toggles the attribute. Same logic as the sidebar toggle, just callingsetAttributeswith the opposite value.
Save the file, rebuild, and refresh the editor. When you select a Notice block, you should now see the close icon in the block toolbar. Click it and the button will toggle between its pressed and unpressed states, and the close button in the block preview will appear or disappear accordingly.

How the Toolbar Button Gets Positioned
You might be wondering how WordPress decides where your custom button shows up in the toolbar. By default, BlockControls places your controls in the main toolbar area alongside any controls that come from block supports (like alignment).
If you need more control over positioning, BlockControls accepts a group prop that determines where your controls appear. The available groups are:
default(or no group specified): The main toolbar area. This is where most custom controls go.block: Appears at the start of the toolbar, before the default group. The block switcher and mover controls live here.inline: Used for inline formatting controls like bold, italic, and link. You probably won’t use this for custom block controls.other: Appears at the end of the toolbar, after the default group. The “More options” menu lives here.
For our dismissible toggle, the default group is the right choice. It puts the button in a natural location where users expect to find block-specific actions.
If you wanted to put it in a different group, we’d just need to specify a different value for the group prop on BlockControls, using one of the above choices.
The syntax looks like this:
<BlockControls group="other">
<ToolbarGroup>
{ /* ... */ }
</ToolbarGroup>
</BlockControls>We don’t need to do this for our use case, but it’s good to know it’s available if you ever build a block with more complex toolbar needs.
Adding a Toolbar Dropdown
We also have our block styles, which currently require someone to select the block, then click the Styles tab, and then choose a style. We could also include an option to do this in the block toolbar as well so we can make those options easier to get to, just like our toggle for isDimissible.
Since this one needs to display a list of styles, we need more of a dropdown menu. Thankfully WordPress provides a ToolbarDropdownMenu component for exactly that.
Here’s a quick example of what that looks like for reference:
import { ToolbarDropdownMenu } from '@wordpress/components';
import { chevronDown } from '@wordpress/icons';
<BlockControls>
<ToolbarDropdownMenu
icon={ chevronDown }
label={ __( 'Select style', 'lwpd' ) }
controls={ [
{
title: __( 'Info', 'lwpd' ),
onClick: () => { /* handle click */ },
},
{
title: __( 'Warning', 'lwpd' ),
onClick: () => { /* handle click */ },
},
// ...
] }
/>
</BlockControls>ToolbarDropdownMenu basically takes all of the same props as the DropdownMenu component. It has a prop called controls that determines what is inside the dropdown menu.
The controls prop accepts an array of objects that need at least 2 properties set: title and onClick. In our case, we’re going to add a few more in the mix as well to account for accessibility and to clearly show which style has been chosen (just like the normal styles interface).
So this means we need to provide an array of objects that indicates what styles there are to choose from. We could just write the values in by hand, but if the styles were to change then we’d need to update this code to suit it, etc.
It would be much better if we could just dynamically read the styles and output dropdown menu items for each automatically. And that’s just what we’re going to do!
Getting the list of styles
The goal here is to provide a simple dropdown menu interface that lets a user choose the style they want to apply. It should be smart enough to understand what styles are already there and clearly show which style is active.
So with that in mind, let’s jump into the edit.js file. Here we’ll start by adding an import for the block.json file:
// Get the JSON file contents
import blockMetadata from './block.json'
// Get the styles can from the JSON file.
const { styles: blockStyles = [] } = blockMetadata || {};
// ....If you look at block.json, you’ll see there is a styles key that we had set up previously. Its value is an array of objects with keys like: name, label, and isDefault. These don’t exactly match up to what the ToolbarDropdownMenu expects, so we’ll need to set up the data for the component to use in a way it understands.
As a reminder, the editor looks for the style class, such as: is-style-warning, to determine if a given block style is being applied. This means we need to look at the className attribute for the block and check if it has such a class inside of it. If it does, we can conclude this particular block style is active.
There’s a few edge cases to account for, like what no style has been chosen at all. In that case, we’ll check if the style has isDefault set to true and if no other style is chosen, that style will be considered active. This is the same behavior that the block editor uses for block styles.
For accessibility, each control object has role: 'menuitemradio'. This role tells screen readers that only one dropdown item may be chosen at a time.
We are also going to add a prop called popoverProps which allows us to pass props to the Popover component used in the DropdownMenu behind the scenes. This allows us to set a class on the wrapper of the popover, which we’ll use so we can style the active dropdown menu item.
We’ll import an icon to use for triggering the dropdown. I’m thinking the styles icon makes the most sense. That icon is the same one used for the Styles tab in the sidebar too so it’s more likely to be recognized by a user. We’ll import that and then pass it to the icon prop for ToolbarDropdownMenu.
Lastly, we’ll implement useMemo here to make sure we’re not rebuilding the set of controls every time the component renders unless the className attribute actually changes. The second argument in useMemo is the dependency array which only has one item: className.
So when that attribute (which is a string) changes, the controls are updated and the currently active one will be set correctly, and so on.
Here’s what edit.js looks like with all of these changes in place:
import { __ } from '@wordpress/i18n';
import {
useBlockProps,
useInnerBlocksProps,
InspectorControls,
BlockControls,
} from '@wordpress/block-editor';
import {
PanelBody,
ToggleControl,
Icon,
ToolbarGroup,
ToolbarButton,
ToolbarDropdownMenu,
} from '@wordpress/components';
import { close, styles } from '@wordpress/icons';
import { useId, useMemo } from '@wordpress/element';
import blockMetadata from './block.json';
const { styles: blockStyles = [] } = blockMetadata || {};
import './editor.scss';
export default function Edit({ attributes, setAttributes }) {
const { isDismissible, anchor } = attributes;
const blockProps = useBlockProps({
'data-dismissible': isDismissible || undefined,
});
const { className } = blockProps;
const innerBlocksProps = useInnerBlocksProps({
className: 'wp-block-lwpd-notice__children',
});
const fallbackId = 'lwpd-notice' + useId();
const controls = useMemo(() => {
return blockStyles.map(({ name, label, isDefault }) => {
const noStyleApplied = !className.includes('is-style-');
return {
title: label,
isActive:
className.includes(`is-style-${name}`) ||
(noStyleApplied && isDefault),
role: 'menuitemradio',
onClick: () => {
const filteredClasses = className
.split(' ')
.filter((name) => !name.startsWith('is-style-'));
filteredClasses.push(`is-style-${name}`);
setAttributes({
className: filteredClasses.join(' '),
});
},
};
});
}, [className]);
return (
<>
<div {...blockProps}>
{isDismissible && (
<button
className="wp-block-lwpd-notice__dismiss"
type="button"
aria-label={__('Dismiss Notice', 'lwpd')}
>
<Icon icon={close} size="24" />
</button>
)}
<div {...innerBlocksProps} />
</div>
<InspectorControls>
<PanelBody title={__('Settings', 'lwpd')}>
<ToggleControl
label={__('Dismissible', 'lwpd')}
help={
isDismissible
? __('Users can dismiss this notice.', 'lwpd')
: __('Notice will always be visible.', 'lwpd')
}
checked={isDismissible}
onChange={(value) => {
const updates = { isDismissible: value };
if (value && !anchor) {
updates.anchor = fallbackId;
}
setAttributes(updates);
}}
/>
</PanelBody>
</InspectorControls>
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon={close}
label={
isDismissible
? __('Remove dismiss button', 'lwpd')
: __('Add dismiss button', 'lwpd')
}
isPressed={isDismissible}
onClick={() => {
const updates = { isDismissible: !isDismissible };
if (!isDismissible && !anchor) {
updates.anchor = fallbackId;
}
setAttributes(updates);
}}
/>
<ToolbarDropdownMenu
icon={styles}
label={__('Select Style', 'lwpd')}
controls={controls}
popoverProps={{
className: 'wp-block-lwpd-notice__popover',
}}
/>
</ToolbarGroup>
</BlockControls>
</>
);
}
Additionally, we’ll need to add some extra styles in editor.scss to style the active dropdown menu item. We’ll use the native WordPress CSS variables and fallbacks (just like the editor itself does) for best compatibility and set the colors just like the native block styles UI in the sidebar.
.wp-block-lwpd-notice {
&[data-dismissible] {
display: block;
}
&__popover {
.components-dropdown-menu__menu-item.is-active {
background-color: var(--wp-components-color-foreground, #1e1e1e);
color: var(--wp-components-color-background, #fff);
}
}
}Once these changes are made and the build is done, you’ll see the new dropdown next to the new toggle we just added:

Quick Recap
Here’s what we covered in this part:
- Discussed when to use toolbar controls versus sidebar controls, and why having the option in both places can be a good user experience.
- Imported
BlockControls,ToolbarGroup, andToolbarButtonto add a custom control to the block toolbar. - Added a dismissible toggle button that uses
isPressedto visually communicate the current state. - Covered the
groupprop onBlockControlsfor controlling where toolbar items are positioned. - Created a custom dropdown menu for the block toolbar that lets you choose the block style
Small touches like keeping frequently used controls accessible in the toolbar (instead of buried in the sidebar) go a long way toward making your block feel polished and thoughtful. When you leverage these core patterns and UI styles, you can create a cohesive UI that’s easier for users to understand and use.
Next Steps
In Part 10, we’ll shift gears and look at how to expand the use of our Notice block from a single page block into a global site feature that shows on specific pages.