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 :) |
