Contents
Overview
This article is a complete, detailed tutorial for adding a custom toolbar to a Gutenberg block in WordPress using JavaScript. It covers creating your own block with a toolbar, adding toolbar controls to existing core blocks via a wrapper (filter HOC), the block.json-based workflow, enqueuing and building the editor script, accessibility considerations, keyboard handling, and troubleshooting. Example code snippets are provided for each step and are ready to drop into a plugin or theme build pipeline that uses @wordpress/scripts (wp-scripts) and an ESNext toolchain.
Prerequisites
- WordPress 5.0 (Gutenberg integrated) — higher is better for recent APIs.
- Node.js and npm (or yarn) — for build tools.
- Familiarity with React/JSX, ES modules, and npm scripts.
- An editor for creating plugin files or a block scaffolding tool (optional).
Concepts and APIs used
- BlockControls (from @wordpress/block-editor) — place controls in the block toolbar area.
- ToolbarButton, ToolbarGroup, ToolbarDropdownMenu (from @wordpress/components) — building blocks of a toolbar.
- useBlockProps and block attributes — reading and updating block state.
- addFilter and createHigherOrderComponent (from @wordpress/hooks and @wordpress/compose) — wrap core block edit to add custom toolbar (for altering existing blocks).
- block.json and register_block_type_from_metadata — metadata-driven block registration.
Project structure (recommended)
- my-plugin/
- package.json
- src/
- block.json
- editor.js (or index.js)
- style.css (for front-end styles)
- editor.css (for editor styles)
- build/ (generated)
- my-plugin.php (plugin bootstrap)
Quick setup (package.json and scripts)
If you use @wordpress/scripts, a basic package.json scripts section looks like:
{ name: my-block-plugin, version: 1.0.0, scripts: { start: wp-scripts start, build: wp-scripts build }, devDependencies: { @wordpress/scripts: ^25.0.0 } }
This example registers a simple block that contains a toolbar with a toggle button which flips a boolean attribute highlighted. When toggled, the block output receives an extra CSS class. The toolbar button appears in the block toolbar (top bar when the block is selected) using BlockControls and ToolbarButton.
block.json
{ apiVersion: 2, name: my-plugin/highlight-block, title: Highlight Block, category: text, icon: admin-customizer, editorScript: file:./build/index.js, attributes: { content: { type: string, source: html, selector: div }, highlighted: { type: boolean, default: false } }, supports: { html: false } }
src/editor.js (block editor code)
import { registerBlockType } from @wordpress/blocks import { useBlockProps, BlockControls, RichText } from @wordpress/block-editor import { ToolbarGroup, ToolbarButton, Icon } from @wordpress/components import { __ } from @wordpress/i18n import { mark } from @wordpress/icons import ./editor.css registerBlockType(my-plugin/highlight-block, { edit: (props) =gt { const { attributes, setAttributes, isSelected } = props const { content, highlighted } = attributes const blockProps = useBlockProps({ className: highlighted ? my-highlighted-block : undefined, }) const toggleHighlight = () =gt { setAttributes({ highlighted: ! highlighted }) } return ( <> {isSelected ()} > ) }, save: (props) =gt { const { attributes } = props const { content, highlighted } = attributes const blockProps = useBlockProps.save({ className: highlighted ? my-highlighted-block : undefined, }) return ( ) } })
CSS (editor.css / style.css)
.my-highlighted-block { background-color: rgba(255, 250, 150, 0.35) border-left: 3px solid #f1c40f padding: 0.5em }
Notes:
- isSelected determines when BlockControls is rendered — you usually only want toolbar when the block is selected.
- ToolbarButton accepts isPressed to reflect a toggled state.
- Use icons from @wordpress/icons or pass an SVG element.
Example 2 — Toolbar with a dropdown and custom actions
Use DropdownMenu (from @wordpress/components) to present multiple related actions inside the toolbar, such as formatting options or pre-set styles.
import { DropdownMenu, ToolbarButton } from @wordpress/components import { BlockControls } from @wordpress/block-editor import { __ } from @wordpress/i18n function MyToolbarDropdown( { setAttributes } ) { const options = [ { label: __( Style A, my-plugin ), onClick: () =gt setAttributes({ style: a }) }, { label: __( Style B, my-plugin ), onClick: () =gt setAttributes({ style: b }) }, ] return ( ltBlockControlsgt ltDropdownMenu icon=admin-appearance label={ __( Choose style, my-plugin ) } controls={ options } /gt lt/BlockControlsgt ) }
Example 3 — Add toolbar controls to a core block (wrap edit)
To extend an existing block (for example, core/paragraph) add a custom toolbar button by wrapping the block edit component using a higher-order component (HOC) and a filter. This approach does not alter core block source code — it injects additional UI into the editor UI when that block is rendered.
src/extend-paragraph-toolbar.js
import { addFilter } from @wordpress/hooks import { createHigherOrderComponent } from @wordpress/compose import { BlockControls } from @wordpress/block-editor import { ToolbarButton } from @wordpress/components import { __ } from @wordpress/i18n import { mark } from @wordpress/icons const addToolbarButton = createHigherOrderComponent( ( BlockEdit ) =gt { return ( props ) =gt { // Only modify paragraph block (change to desired block name) if ( props.name !== core/paragraph ) { return ltBlockEdit { ...props } /gt } const { attributes, setAttributes, isSelected } = props const highlighted = attributes?.myCustomHighlight ?? false const toggle = () =gt { setAttributes( { myCustomHighlight: ! highlighted } ) } return ( <> ltBlockEdit { ...props } /gt { isSelected ampamp ( ltBlockControls group=blockgt ltToolbarButton icon={ mark } label={ __( Toggle highlight, my-plugin ) } isPressed={ highlighted } onClick={ toggle } /gt lt/BlockControlsgt )} > ) } }, addToolbarButton ) // Hook into editor to extend the edit UI addFilter( editor.BlockEdit, my-plugin/extend-paragraph-toolbar, addToolbarButton )
Important: since core block attributes are not modified in the core block definition, you may want to add attributes by registering a filter on blocks.registerBlockType to inject attributes. Otherwise attributes saved wont be reflected in front-end output unless you persist them in save markup or an attribute source.
Adding attributes to an existing block (optional)
import { addFilter } from @wordpress/hooks function addAttributes( settings, name ) { if ( name !== core/paragraph ) { return settings } // Merge new attribute into block settings settings.attributes = Object.assign( {}, settings.attributes, { myCustomHighlight: { type: boolean, default: false } } ) return settings } addFilter( blocks.registerBlockType, my-plugin/paragraph-attributes, addAttributes )
Registering and enqueuing the script (PHP)
If you use block.json with editorScript: file:./build/index.js, WordPress can register the script automatically when you call register_block_type_from_metadata. If not, you can register/enqueue manually.
If you prefer manual registration with an editor script handle:
function my_plugin_enqueue_files() { asset_file = include plugin_dir_path( __FILE__ ) . build/index.asset.php wp_register_script( my-plugin-editor, plugins_url( build/index.js, __FILE__ ), asset_file[dependencies], asset_file[version] ) register_block_type( __DIR__ . /build, array( editor_script => my-plugin-editor, ) ) } add_action( init, my_plugin_enqueue_files )
Build and bundle
- Run npm install and then npm run build (or npm run start for dev watching) using wp-scripts.
- Ensure index.asset.php is generated and contains dependencies and version (wp-scripts does this for you).
- Place the built files in build/ and verify plugin is active in WP admin.
Accessibility and keyboard behavior
- ToolbarButton automatically supports keyboard focus and activation. Use label for screen-readers via prop label.
- DropdownMenu supports keyboard navigation. Ensure menu item labels are descriptive.
- When opening custom popovers, manage focus properly: focus the first focusable element and restore focus when closed.
- Always provide aria-label or label props for buttons and include visible state via isPressed so screen readers can read the state.
Best practices and details
- Only show BlockControls when the block is selected — reduces visual clutter. Use isSelected prop or check selection state.
- Group related toolbar buttons with ToolbarGroup to maintain consistent styling and spacing.
- Keep toolbar actions lightweight — heavy UI should go in InspectorControls or a modal/popover to avoid blocking the editor toolbar space.
- Persist attributes if the toolbar modifies display or semantics. Use attributes in save() or mark attributes with source to output in markup.
- Icons — prefer @wordpress/icons for consistent look. For custom SVG, pass JSX
- Use stateful components cautiously — manage state in block attributes if the UI must persist between saves.
- For core block extensions add attributes via blocks.registerBlockType filter so the block can save the new attribute or use meta if you prefer post meta storage.
Troubleshooting
- If your toolbar doesnt appear: ensure BlockControls is rendered within the block edit markup and isSelected is true while selecting the block.
- If script fails to load: check index.asset.php dependencies and that your build path or handle is correct.
- If attributes are not saved: confirm attributes are defined in block.json or injected via blocks.registerBlockType filter and used in save().
- Check browser console for JSX syntax errors or missing imports. Make sure your bundler supports JSX and ESNext code.
Advanced techniques
- Contextual toolbars — change toolbar contents based on selection inside the block (e.g., when a specific child element is selected). Use selection state and inner blocks APIs.
- Floating toolbars or inline formatting — combine RichText formatting controls and custom popovers for inline UI.
- Use Slot / Fill to inject UI into other editor areas, not just block toolbars.
- Server-side rendering — use server-side rendering if a toolbar action requires server computation (AJAX) and then update attributes via setAttributes.
Complete example recap (files)
Minimal files you need:
- block.json — metadata (editorScript points to built JS)
- src/editor.js — block registration and edit/save code with BlockControls and ToolbarButton
- src/editor.css and src/style.css — styles for editor and front-end
- my-plugin.php — register_block_type_from_metadata or enqueue built script
- package.json — wp-scripts build tools
Example: Full JS plugin entry that registers both a custom block and a paragraph toolbar extension
/ src/index.js Entry file that registers a custom block and extends core/paragraph toolbar. / import ./block // your custom block registration (editor.js example from earlier) import ./extend-paragraph-toolbar // HOC filter for core/paragraph extension
Security considerations
- Sanitize any user-entered content that will be saved to attributes if you output it as HTML use appropriate escaping.
- Avoid injecting arbitrary HTML into the editor without sanitization.
- Only provide capabilities to users who should access the toolbar actions if they perform privilege-sensitive operations — check current_user_can on server requests.
Performance tips
- Keep toolbar components small and avoid heavy computations in render. Use memoization if necessary.
- Only render toolbar UI when the block is selected. Conditional rendering greatly reduces re-renders in the editor.
- Avoid global event listeners in toolbar components that persist beyond unmount clean up in useEffect cleanup functions.
Useful links
Summary
Adding a custom toolbar to a block in the Gutenberg editor is primarily about using BlockControls and the components in @wordpress/components. You can either build the toolbar into a custom block or extend a core block via a filter HOC. Use attributes to persist state, provide accessible labels and pressed states, and register/enqueue your scripts using block.json or PHP functions. With wp-scripts, bundling and building are straightforward. The examples above provide the exact code patterns you need for toggles, dropdowns, and extending core blocks.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |