Contents
Overview
This article explains, in complete detail, how to add controls to InspectorControls in a WordPress block (JavaScript). It covers required imports, block attributes, the edit UI, common control components, grouping controls into panels, using advanced inspector controls, saving the result for the front-end, dynamic-rendered blocks, accessibility and best practices, and troubleshooting. Multiple working examples are included (editor and front-end) and each code example is provided using the required pre tag format.
Prerequisites
- WordPress 5.0 (for modern block editor APIs). For best compatibility use a recent WP version.
- Node.js and npm when developing using @wordpress/scripts and modern ESNext with JSX.
- Basic knowledge of registerBlockType and React hooks is recommended.
- Familiarity with block attributes and the difference between editor-only controls and saved output.
Concepts and key points
- InspectorControls is a component from @wordpress/block-editor. Controls placed inside it appear in the block inspector (sidebar) when the block is selected.
- Controls inside InspectorControls affect block attributes through setAttributes and therefore the editable block content or saved markup.
- InspectorControls are editor-only. They do not render on the front-end. If you need visual changes on the front-end, you must use attributes and reflect them in the save() or the PHP render callback.
- Useful control components are in @wordpress/components and include: PanelBody, ToggleControl, TextControl, RangeControl, SelectControl, ColorPalette, ColorPicker, FontSizePicker, BaseControl, PanelRow, and QueryControls.
- Use InspectorAdvancedControls for placement under the Advanced panel (where additional fields like HTML anchor often go).
- Always use useBlockProps to attach block classes and attributes in edit and save.
Basic imports and skeleton
A typical block using InspectorControls will import these essentials:
- registerBlockType (or use block.json register automatically)
- InspectorControls and useBlockProps from @wordpress/block-editor
- PanelBody, ToggleControl, TextControl, RangeControl, etc. from @wordpress/components
- __ from @wordpress/i18n for translations
Example skeleton of an edit component with InspectorControls:
import { __ } from @wordpress/i18n import { useBlockProps, InspectorControls } from @wordpress/block-editor import { PanelBody, ToggleControl, TextControl } from @wordpress/components export default function Edit( { attributes, setAttributes } ) { const blockProps = useBlockProps() return ( <>ltPanelBody title=Settingsgt ltToggleControl label=Show title checked={ attributes.showTitle } onChange={ ( value ) =gt setAttributes( { showTitle: value } ) } /gt ltTextControl label=Subtitle value={ attributes.subtitle } onChange={ ( value ) =gt setAttributes( { subtitle: value } ) } /gt lt/PanelBodygt ltdiv { ...blockProps } gt { attributes.showTitle lth2gtExample Titlelt/h2gt } ltpgt{ attributes.subtitle }lt/pgt lt/divgt > ) }
Important notes about the skeleton
- InspectorControls can contain anything from the component library they must not appear in the save() output.
- Use a Fragment (<>…>) to include both InspectorControls and the visible editor UI in the edit() output.
- Attributes must be registered for your block so changes persist.
Registering attributes
Attributes define the data a block stores. Define attributes for every control you put in InspectorControls. Typical attribute shapes include string, boolean, number, object. Store values either inline in comment attributes or in markup (source: text, selector). For many simple controls, use comment attributes (default).
registerBlockType( my-plugin/inspector-example, { title: Inspector Example, category: widgets, attributes: { subtitle: { type: string, default: A short subtitle, }, showTitle: { type: boolean, default: true, }, fontSize: { type: number, default: 16, }, backgroundColor: { type: string, default: #ffffff, }, }, edit: Edit, save: Save, } )
Basic example: ToggleControl and TextControl
This example creates two controls in InspectorControls: a ToggleControl toggling the title and a TextControl for a subtitle. The save function outputs HTML reflecting the attributes.
/ Editor file: src/edit.js / import { __ } from @wordpress/i18n import { useBlockProps, InspectorControls } from @wordpress/block-editor import { PanelBody, ToggleControl, TextControl } from @wordpress/components export default function Edit( { attributes, setAttributes } ) { const { showTitle, subtitle } = attributes const blockProps = useBlockProps() return ( <>ltPanelBody title={ __( Block Settings, text-domain ) } initialOpen={ true }gt ltToggleControl label={ __( Show title, text-domain ) } checked={ showTitle } onChange={ ( value ) =gt setAttributes( { showTitle: value } ) } /gt ltTextControl label={ __( Subtitle, text-domain ) } value={ subtitle } onChange={ ( value ) =gt setAttributes( { subtitle: value } ) } /gt lt/PanelBodygt ltdiv { ...blockProps } gt { showTitle lth2gt{ __( My Block Title, text-domain ) }lt/h2gt } ltpgt{ subtitle }lt/pgt lt/divgt > ) } / Save file: src/save.js / import { useBlockProps } from @wordpress/block-editor export default function Save( { attributes } ) { const { showTitle, subtitle } = attributes const blockProps = useBlockProps.save() return ( ltdiv { ...blockProps } gt { showTitle lth2gtMy Block Titlelt/h2gt } ltpgt{ subtitle }lt/pgt lt/divgt ) }
Advanced controls and layout
When you need multiple controls and logical grouping, use PanelBody and PanelRow. Add panels for styles, colors, and advanced options. Use InspectorAdvancedControls for Advanced panel placement (below the main inspector sections).
import { InspectorControls, InspectorAdvancedControls } from @wordpress/block-editor import { PanelBody, PanelRow, RangeControl, SelectControl, ColorPalette, ColorPicker, ToggleControl, } from @wordpress/components function Edit( { attributes, setAttributes } ) { const { fontSize, alignment, backgroundColor, showBorder } = attributes return ( <> ltInspectorControlsgt ltPanelBody title=Typographygt ltPanelRowgt ltRangeControl label=Font size value={ fontSize } onChange={ ( value ) =gt setAttributes( { fontSize: value } ) } min={ 10 } max={ 72 } /gt lt/PanelRowgt ltPanelRowgt ltSelectControl label=Text alignment value={ alignment } options={ [ { label: Left, value: left }, { label: Center, value: center }, { label: Right, value: right }, ] } onChange={ ( value ) =gt setAttributes( { alignment: value } ) } /gt lt/PanelRowgt lt/PanelBodygt ltPanelBody title=Colors initialOpen={ false }gt ltPanelRowgt ltColorPalette value={ backgroundColor } onChange={ ( color ) =gt setAttributes( { backgroundColor: color } ) } /gt lt/PanelRowgt lt/PanelBodygt lt/InspectorControlsgt ltInspectorAdvancedControlsgt ltToggleControl label=Show border checked={ showBorder } onChange={ ( value ) =gt setAttributes( { showBorder: value } ) } /gt lt/InspectorAdvancedControlsgt ltdiv style={ { fontSize: fontSize px, textAlign: alignment, backgroundColor: backgroundColor, border: showBorder ? 1px solid #ddd : none, } } gt Editable content here lt/divgt > ) }
Dynamic rendering vs static save()
InspectorControls only appear in the editor. If your block is static, reflect attributes in save(). If you require dynamic server-side output (PHP rendering), register a render callback in PHP and pass attributes through the InspectorControls remain in the editor, but the final markup is produced by PHP.
/ PHP registration example for dynamic rendering / function my_plugin_register_dynamic_block() { register_block_type( __DIR__ . /build/, array( render_callback => my_plugin_render_dynamic_block, ) ) } add_action( init, my_plugin_register_dynamic_block ) function my_plugin_render_dynamic_block( attributes, content ) { subtitle = isset( attributes[subtitle] ) ? esc_html( attributes[subtitle] ) : show_title = isset( attributes[showTitle] ) attributes[showTitle] ? true : false font_size = isset( attributes[fontSize] ) ? intval( attributes[fontSize] ) : 16 bg = isset( attributes[backgroundColor] ) ? esc_attr( attributes[backgroundColor] ) : #fff style = font-size: {font_size}px background-color: {bg} html = ltdiv class=my-plugin-block style=. style .>lt if ( show_title ) { html .= lth2gt. esc_html__( My Block Title, text-domain ) .lt/h2gt } html .= ltpgt . subtitle . lt/pgt html .= lt/divgt return html }
Example: Using ColorPicker, RangeControl, and SelectControl
This sample demonstrates several controls together and how to apply the selected styles to both the editor preview and the saved markup.
/ src/edit.js / import { __ } from @wordpress/i18n import { useBlockProps, InspectorControls } from @wordpress/block-editor import { PanelBody, RangeControl, SelectControl, ColorPicker } from @wordpress/components export default function Edit( { attributes, setAttributes } ) { const { fontSize, alignment, backgroundColor } = attributes const blockProps = useBlockProps( { style: { fontSize: fontSize ? fontSize px : undefined, textAlign: alignment, backgroundColor: backgroundColor, padding: 12px, }, } ) return ( <> ltInspectorControlsgt ltPanelBody title={ __( Style, text-domain ) } gt ltRangeControl label={ __( Font Size, text-domain ) } value={ fontSize } onChange={ ( value ) =gt setAttributes( { fontSize: value } ) } min={ 10 } max={ 72 } /gt ltSelectControl label={ __( Alignment, text-domain ) } value={ alignment } onChange={ ( value ) =gt setAttributes( { alignment: value } ) } options={ [ { label: Left, value: left }, { label: Center, value: center }, { label: Right, value: right }, ] } /gt ltColorPicker color={ backgroundColor } onChangeComplete={ ( color ) =gt setAttributes( { backgroundColor: color.hex } ) } disableAlpha /gt lt/PanelBodygt lt/InspectorControlsgt ltdiv { ...blockProps } gt lth3gtPreview Textlt/h3gt ltpgtThis area reflects your InspectorControls selection.lt/pgt lt/divgt > ) } / src/save.js / import { useBlockProps } from @wordpress/block-editor export default function Save( { attributes } ) { const { fontSize, alignment, backgroundColor } = attributes const blockProps = useBlockProps.save( { style: { fontSize: fontSize ? fontSize px : undefined, textAlign: alignment, backgroundColor: backgroundColor, padding: 12px, }, } ) return ( ltdiv { ...blockProps } gt lth3gtSaved Block Titlelt/h3gt ltpgtSaved content reflecting inspector settings.lt/pgt lt/divgt ) }
Inspector controls that require asynchronous data: QueryControls and special cases
Some controls interact with the REST API or dynamic data. For example, QueryControls allow selecting posts or categories. Use useSelect or the higher-order withSelect where needed. Keep performance in mind and avoid heavy requests on every render. Use useEffect to fetch only when necessary.
import { useSelect } from @wordpress/data import { QueryControls } from @wordpress/components import { useEntityRecords } from @wordpress/core-data function PostSelectorControl( { value, onChange } ) { const posts = useSelect( ( select ) =gt select( core ).getEntityRecords( postType, post, { per_page: 10 } ), [] ) return ( ltQueryControls numberOfItems={ posts ? posts.length : 0 } isLoading={ ! posts } onQueryChange={ ( query ) =gt { // Implement filtering logic or request more items } } /gt ) }
Best practices
- Group logically: place related settings into PanelBody sections (e.g., Typography, Colors, Layout).
- Use sensible defaults: attributes should have defaults so the UI is predictable.
- Persist only what you need: avoid storing derived state in attributes — compute it where possible.
- Use useBlockProps: this ensures block classes and wrappers are consistent in both editor and front-end.
- Don’t put editor-only content into save(): InspectorControls belong only in the editor. Save should output markup based on attributes.
- Accessibility: use labels on controls and ensure contrast for color pickers or previews.
- Performance: avoid heavy fetches in edit() without memoization. Use useSelect/useEffect carefully.
- Internationalization: wrap admin-facing strings with __() or sprintf() as appropriate.
Common mistakes and troubleshooting
- InspectorControls not showing: ensure your edit() returns a Fragment with InspectorControls and that the block is selected. Also check imports: it must come from @wordpress/block-editor.
- Attribute changes not persisting: confirm attributes are registered in registerBlockType and that you call setAttributes with the correct keys.
- Editor preview not styled like saved markup: ensure you pass inline styles or classes from attributes to both edit and save using useBlockProps and consistent rendered HTML.
- Control UI not updating: check that the controls value prop is wired to attributes and that onChange calls setAttributes with the appropriate type (cast numbers from strings when needed).
- Using InspectorControls in save(): it will not display in the front-end and should not be used there add UI only in edit().
Extra tips
- Use InspectorAdvancedControls when you want items in the Advanced panel (below the main panels).
- Preview changes visually in the editor by applying inline styles or classes bound to attributes if using dynamic PHP rendering, mirror the same logic there.
- When working with color values, consider offering presets as well as free color picker choices.
- For complex controls, create small subcomponents inside your block to keep edit() tidy and re-usable.
- Test mobile and responsive behaviors since some controls like font size may need breakpoints.
Complete practical example: Registering a block with InspectorControls and PHP render
This final example demonstrates a full workflow: block registration (JS), editor (InspectorControls with multiple controls), and server-side rendering via PHP.
/ src/index.js / import { registerBlockType } from @wordpress/blocks import Edit from ./edit import ./style.scss registerBlockType( my-plugin/advanced-inspector-block, { apiVersion: 2, title: Advanced Inspector Block, category: design, attributes: { subtitle: { type: string, default: Editable subtitle }, showTitle: { type: boolean, default: true }, fontSize: { type: number, default: 18 }, backgroundColor: { type: string, default: #ffffff }, align: { type: string, default: left }, }, edit: Edit, save: () =gt null, // use server-side render } )
/ src/edit.js / import { __ } from @wordpress/i18n import { useBlockProps, InspectorControls, InspectorAdvancedControls } from @wordpress/block-editor import { PanelBody, ToggleControl, TextControl, RangeControl, SelectControl, ColorPalette } from @wordpress/components export default function Edit( { attributes, setAttributes } ) { const { subtitle, showTitle, fontSize, backgroundColor, align } = attributes const blockProps = useBlockProps( { style: { fontSize: fontSize px, backgroundColor, textAlign: align, padding: 12px, }, } ) const colors = [ { name: White, color: #ffffff }, { name: Light Gray, color: #f0f0f0 }, { name: Beige, color: #f5f0e1 }, { name: Blue, color: #e6f0ff }, ] return ( <> ltInspectorControlsgt ltPanelBody title={ __( Content, text-domain ) } initialOpen={ true }gt ltTextControl label={ __( Subtitle, text-domain ) } value={ subtitle } onChange={ ( val ) =gt setAttributes( { subtitle: val } ) } /gt ltToggleControl label={ __( Show Title, text-domain ) } checked={ showTitle } onChange={ ( val ) =gt setAttributes( { showTitle: val } ) } /gt lt/PanelBodygt ltPanelBody title={ __( Style, text-domain ) } initialOpen={ false }gt ltRangeControl label={ __( Font size, text-domain ) } value={ fontSize } onChange={ ( val ) =gt setAttributes( { fontSize: val } ) } min={ 12 } max={ 48 } /gt> ltSelectControl label={ __( Text Align, text-domain ) } value={ align } options={ [ { label: Left, value: left }, { label: Center, value: center }, { label: Right, value: right }, ] } onChange={ ( val ) =gt setAttributes( { align: val } ) } /gt ltdiv style={ { marginTop: 12px } }gt ltlabel style={ { display: block, marginBottom: 8px } }gtBackgroundlt/labelgt ltColorPalette colors={ colors } value={ backgroundColor } onChange={ ( val ) =gt setAttributes( { backgroundColor: val } ) } /gt lt/divgt lt/PanelBodygt lt/InspectorControlsgt ltInspectorAdvancedControlsgt ltdivgt ltsmallgtAdvanced options could go herelt/smallgt lt/divgt lt/InspectorAdvancedControlsgt ltdiv { ...blockProps } gt { showTitle lth2gt{ __( Block Title, text-domain ) }lt/h2gt } ltpgt{ subtitle }lt/pgt lt/divgt > ) }
/ server-side render in PHP: my-plugin.php / function my_plugin_register_block() { register_block_type( __DIR__ . /build, array( render_callback =gt my_plugin_render_block, ) ) } add_action( init, my_plugin_register_block ) function my_plugin_render_block( attributes ) { subtitle = isset( attributes[subtitle] ) ? wp_kses_post( attributes[subtitle] ) : show_title = ! empty( attributes[showTitle] ) ? true : false font_size = ! empty( attributes[fontSize] ) ? intval( attributes[fontSize] ) : 18 bg = ! empty( attributes[backgroundColor] ) ? esc_attr( attributes[backgroundColor] ) : #ffffff align = ! empty( attributes[align] ) ? esc_attr( attributes[align] ) : left style = sprintf( font-size:%spxbackground-color:%stext-align:%spadding:12px, font_size, bg, align ) ob_start() ?>>Useful links and references
Summary
InspectorControls are the correct place for settings that affect the block but are not part of the inline editable content. Use the components in @wordpress/components to build a friendly settings UI. Keep attributes well-defined, use useBlockProps, group controls into PanelBody blocks, and remember that InspectorControls are editor-only. For front-end output, either reflect attributes in save() or implement server-side rendering in PHP.
|
Acepto donaciones de BAT's mediante el navegador Brave :) |