gutenberg-responsive-control
v1.0.1
Published
A responsive device control component for WordPress Gutenberg blocks
Maintainers
Readme
gutenberg-responsive-control
A lightweight, plug-and-play responsive device switcher for WordPress Gutenberg blocks.
- Zero required props — works out of the box without
setAttributesor any block attribute. - localStorage-backed — the selected device persists across page reloads and is shared across all blocks on the same page.
- Editor-aware — automatically syncs with the WordPress editor's built-in device preview toolbar (Desktop / Tablet / Mobile).
- Optional
resModeattribute — passsetAttributesonly when you want to store the device type as a block attribute. - Two APIs — a ready-made
<ResponsiveControl>component or a headlessuseDeviceType()hook for custom UIs.
Table of Contents
- Requirements
- Installation
- CSS Setup
- Quick Start
- API Reference
- Use Cases
- How Device State Works
- Full Block Example
Requirements
| Dependency | Version |
|---|---|
| WordPress | 6.0 or later |
| @wordpress/data | 9.0 or later (bundled with WordPress) |
| @wordpress/element | 5.0 or later (bundled with WordPress) |
| Build tool | @wordpress/scripts or any webpack setup |
These WordPress packages are not installed as npm dependencies — they are provided by WordPress itself at runtime through window.wp. You do not need to install them separately.
Installation
npm install gutenberg-responsive-controlCSS Setup
The component styles are pre-compiled and shipped with the package. Choose one of the following methods to load them.
Option A — Import in your block's JavaScript or SCSS (recommended)
// In your block's index.js or editor.js
import 'gutenberg-responsive-control/build/style-index.css';// Or in your block's editor.scss
@import 'gutenberg-responsive-control/src/style';Option B — Enqueue via PHP
function my_block_enqueue_assets() {
wp_enqueue_style(
'gutenberg-responsive-control',
plugin_dir_url( __FILE__ ) . 'node_modules/gutenberg-responsive-control/build/style-index.css',
[],
'1.0.0'
);
}
add_action( 'enqueue_block_editor_assets', 'my_block_enqueue_assets' );Quick Start
import { ResponsiveControl } from 'gutenberg-responsive-control';
import 'gutenberg-responsive-control/build/style-index.css';
const Edit = ( { attributes, setAttributes } ) => {
const { padding } = attributes;
return (
<InspectorControls>
<PanelBody title="Spacing">
<ResponsiveControl label="Padding">
{/* Render different controls per device */}
</ResponsiveControl>
</PanelBody>
</InspectorControls>
);
};That is all you need. No block attributes required, no setAttributes required.
API Reference
ResponsiveControl Component
import { ResponsiveControl } from 'gutenberg-responsive-control';Props
| Prop | Type | Required | Description |
|---|---|---|---|
| label | string | No | Text label displayed to the left of the device buttons. |
| children | ReactNode | No | Controls rendered below the device switcher row. |
| setAttributes | Function | No | The block's setAttributes. Provide only when you want to keep a resMode attribute in sync with the selected device. |
Basic usage (no block attribute needed)
<ResponsiveControl label="Padding">
<p>Content changes based on device</p>
</ResponsiveControl>With optional resMode attribute
<ResponsiveControl label="Padding" setAttributes={ setAttributes }>
<p>Device stored in block attributes as resMode</p>
</ResponsiveControl>useDeviceType Hook
For when you need the device type without the visual switcher UI, or want to build your own UI.
import { useDeviceType } from 'gutenberg-responsive-control';Signature
useDeviceType( setAttributes? : Function ) : [ deviceType: string, setDeviceType: Function ]Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| setAttributes | Function | No | Optional. Pass the block's setAttributes to keep a resMode attribute in sync. |
Returns
| Value | Type | Description |
|---|---|---|
| deviceType | string | 'Desktop', 'Tablet', or 'Mobile' |
| setDeviceType | Function | Call with a device name string to switch the active device. |
Example
const [ deviceType, setDeviceType ] = useDeviceType();
// deviceType === 'Desktop' | 'Tablet' | 'Mobile'
// setDeviceType( 'Tablet' ) switches the deviceUse Cases
1. Responsive Spacing Controls
Show different RangeControl inputs per device. Each device's value is stored independently in block attributes.
import { ResponsiveControl } from 'gutenberg-responsive-control';
import { useDeviceType } from 'gutenberg-responsive-control';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
const Edit = ( { attributes, setAttributes } ) => {
const { paddingDesktop, paddingTablet, paddingMobile } = attributes;
const [ deviceType ] = useDeviceType();
const paddingMap = {
Desktop: { value: paddingDesktop, key: 'paddingDesktop' },
Tablet: { value: paddingTablet, key: 'paddingTablet' },
Mobile: { value: paddingMobile, key: 'paddingMobile' },
};
const { value, key } = paddingMap[ deviceType ];
return (
<InspectorControls>
<PanelBody title="Spacing">
<ResponsiveControl label="Padding">
<RangeControl
label={ `Padding — ${ deviceType }` }
value={ value }
onChange={ ( newValue ) => setAttributes( { [ key ]: newValue } ) }
min={ 0 }
max={ 100 }
/>
</ResponsiveControl>
</PanelBody>
</InspectorControls>
);
};
// block.json attributes
{
"attributes": {
"paddingDesktop": { "type": "number", "default": 16 },
"paddingTablet": { "type": "number", "default": 12 },
"paddingMobile": { "type": "number", "default": 8 }
}
}2. Responsive Typography
Control font size per device with a single control that adapts to the active device.
import { ResponsiveControl, useDeviceType } from 'gutenberg-responsive-control';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
const Edit = ( { attributes, setAttributes } ) => {
const { fontSizeDesktop, fontSizeTablet, fontSizeMobile } = attributes;
const [ deviceType ] = useDeviceType();
const fontSizeValues = {
Desktop: fontSizeDesktop,
Tablet: fontSizeTablet,
Mobile: fontSizeMobile,
};
const fontSizeKeys = {
Desktop: 'fontSizeDesktop',
Tablet: 'fontSizeTablet',
Mobile: 'fontSizeMobile',
};
return (
<InspectorControls>
<PanelBody title="Typography">
<ResponsiveControl label="Font Size">
<RangeControl
label={ `Font Size (${ deviceType })` }
value={ fontSizeValues[ deviceType ] }
onChange={ ( val ) =>
setAttributes( { [ fontSizeKeys[ deviceType ] ]: val } )
}
min={ 10 }
max={ 80 }
/>
</ResponsiveControl>
</PanelBody>
</InspectorControls>
);
};
// block.json
{
"attributes": {
"fontSizeDesktop": { "type": "number", "default": 18 },
"fontSizeTablet": { "type": "number", "default": 16 },
"fontSizeMobile": { "type": "number", "default": 14 }
}
}3. Responsive Visibility Toggle
Hide or show the block on specific devices. Store a visibility flag per device.
import { ResponsiveControl, useDeviceType } from 'gutenberg-responsive-control';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, ToggleControl } from '@wordpress/components';
const Edit = ( { attributes, setAttributes } ) => {
const { hideOnDesktop, hideOnTablet, hideOnMobile } = attributes;
const [ deviceType ] = useDeviceType();
const hideMap = {
Desktop: { value: hideOnDesktop, key: 'hideOnDesktop' },
Tablet: { value: hideOnTablet, key: 'hideOnTablet' },
Mobile: { value: hideOnMobile, key: 'hideOnMobile' },
};
const { value, key } = hideMap[ deviceType ];
return (
<InspectorControls>
<PanelBody title="Visibility">
<ResponsiveControl label="Hide on">
<ToggleControl
label={ `Hide on ${ deviceType }` }
checked={ value }
onChange={ ( checked ) => setAttributes( { [ key ]: checked } ) }
/>
</ResponsiveControl>
</PanelBody>
</InspectorControls>
);
};
// block.json
{
"attributes": {
"hideOnDesktop": { "type": "boolean", "default": false },
"hideOnTablet": { "type": "boolean", "default": false },
"hideOnMobile": { "type": "boolean", "default": false }
}
}4. Using the Hook Directly (Custom UI)
When you want your own device-switching UI instead of the built-in buttons.
import { useDeviceType } from 'gutenberg-responsive-control';
import { ButtonGroup, Button } from '@wordpress/components';
const MyCustomResponsiveSwitcher = ( { label, children } ) => {
const [ deviceType, setDeviceType ] = useDeviceType();
return (
<div>
<div style={ { display: 'flex', alignItems: 'center', gap: 8 } }>
<span>{ label }</span>
<ButtonGroup>
{ [ 'Desktop', 'Tablet', 'Mobile' ].map( ( device ) => (
<Button
key={ device }
variant={ deviceType === device ? 'primary' : 'secondary' }
onClick={ () => setDeviceType( device ) }
size="small"
>
{ device }
</Button>
) ) }
</ButtonGroup>
</div>
{ children }
</div>
);
};5. Storing Device Type as a Block Attribute (resMode)
Use this when your save() function or PHP render needs to know which device was active when the block was last saved. Pass setAttributes to ResponsiveControl and declare resMode in block.json.
// edit.js
import { ResponsiveControl } from 'gutenberg-responsive-control';
const Edit = ( { attributes, setAttributes } ) => {
return (
<InspectorControls>
<PanelBody title="Layout">
{/*
* Passing setAttributes tells the control to keep
* attributes.resMode in sync with the active device.
*/}
<ResponsiveControl
label="Columns"
setAttributes={ setAttributes }
>
{/* your controls here */}
</ResponsiveControl>
</PanelBody>
</InspectorControls>
);
};// block.json — declare the attribute
{
"attributes": {
"resMode": {
"type": "string",
"default": "Desktop"
}
}
}// save.js — use resMode in the saved markup
const Save = ( { attributes } ) => {
const { resMode } = attributes;
return (
<div
{ ...useBlockProps.save() }
data-res-mode={ resMode }
>
{ /* ... */ }
</div>
);
};// render.php — use resMode on the server side
<?php
$res_mode = isset( $attributes['resMode'] ) ? $attributes['resMode'] : 'Desktop';
?>
<div data-res-mode="<?php echo esc_attr( $res_mode ); ?>">
<?php // ... ?>
</div>6. Reading deviceType Outside the Component
If you need the current device type in a helper function or outside of the ResponsiveControl, use the hook anywhere in your block's edit tree.
import { useDeviceType } from 'gutenberg-responsive-control';
// In any child component inside the block edit
const BlockPreview = () => {
const [ deviceType ] = useDeviceType();
const wrapperStyle = {
maxWidth: deviceType === 'Desktop' ? '100%'
: deviceType === 'Tablet' ? '768px'
: '375px',
margin: '0 auto',
};
return (
<div style={ wrapperStyle }>
{ /* preview content */ }
</div>
);
};Because deviceType is backed by localStorage and the WordPress editor store, every component that calls useDeviceType() anywhere in the editor will always read the same value and re-render together when it changes.
How Device State Works
User clicks a device button
│
▼
setDeviceType('Tablet')
│
├── 1. Updates React state immediately → UI re-renders instantly
├── 2. Writes to localStorage → persists across reloads
├── 3. Dispatches to core/editor store → syncs WP editor toolbar
└── 4. Calls setAttributes({ resMode }) → only if setAttributes was passed
WordPress editor toolbar changes device (external)
│
▼
useSelect detects editorDeviceType change
│
├── 1. Updates React state → UI re-renders
└── 2. Writes to localStorage → keeps localStorage in syncThe localStorage key is grc_device_type. It is shared across all blocks on the same editor page, so switching device in one block's ResponsiveControl automatically updates all other blocks using this package.
Full Block Example
A complete, working block that uses responsive padding, font size, and visibility controls together.
block.json
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 3,
"name": "my-plugin/my-block",
"title": "My Responsive Block",
"category": "text",
"editorScript": "file:./index.js",
"editorStyle": "file:./editor.css",
"style": "file:./style.css",
"attributes": {
"content": {
"type": "string",
"default": "Hello World"
},
"paddingDesktop": { "type": "number", "default": 24 },
"paddingTablet": { "type": "number", "default": 16 },
"paddingMobile": { "type": "number", "default": 12 },
"fontSizeDesktop": { "type": "number", "default": 18 },
"fontSizeTablet": { "type": "number", "default": 16 },
"fontSizeMobile": { "type": "number", "default": 14 },
"hideOnMobile": { "type": "boolean", "default": false }
}
}edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls, RichText } from '@wordpress/block-editor';
import { PanelBody, RangeControl, ToggleControl } from '@wordpress/components';
import { ResponsiveControl, useDeviceType } from 'gutenberg-responsive-control';
import 'gutenberg-responsive-control/build/style-index.css';
export default function Edit( { attributes, setAttributes } ) {
const {
content,
paddingDesktop, paddingTablet, paddingMobile,
fontSizeDesktop, fontSizeTablet, fontSizeMobile,
hideOnMobile,
} = attributes;
const [ deviceType ] = useDeviceType();
// Pick the right attribute for the active device
const padding = { Desktop: paddingDesktop, Tablet: paddingTablet, Mobile: paddingMobile };
const fontSize = { Desktop: fontSizeDesktop, Tablet: fontSizeTablet, Mobile: fontSizeMobile };
const paddingKeys = { Desktop: 'paddingDesktop', Tablet: 'paddingTablet', Mobile: 'paddingMobile' };
const fontSizeKeys = { Desktop: 'fontSizeDesktop', Tablet: 'fontSizeTablet', Mobile: 'fontSizeMobile' };
// Live preview style in the editor
const blockStyle = {
padding: padding[ deviceType ],
fontSize: fontSize[ deviceType ],
};
return (
<>
<InspectorControls>
<PanelBody title={ __( 'Spacing', 'my-plugin' ) }>
<ResponsiveControl label={ __( 'Padding', 'my-plugin' ) }>
<RangeControl
label={ `${ __( 'Padding', 'my-plugin' ) } (${ deviceType })` }
value={ padding[ deviceType ] }
onChange={ ( val ) =>
setAttributes( { [ paddingKeys[ deviceType ] ]: val } )
}
min={ 0 }
max={ 80 }
/>
</ResponsiveControl>
</PanelBody>
<PanelBody title={ __( 'Typography', 'my-plugin' ) }>
<ResponsiveControl label={ __( 'Font Size', 'my-plugin' ) }>
<RangeControl
label={ `${ __( 'Font Size', 'my-plugin' ) } (${ deviceType })` }
value={ fontSize[ deviceType ] }
onChange={ ( val ) =>
setAttributes( { [ fontSizeKeys[ deviceType ] ]: val } )
}
min={ 10 }
max={ 60 }
/>
</ResponsiveControl>
</PanelBody>
<PanelBody title={ __( 'Visibility', 'my-plugin' ) }>
<ToggleControl
label={ __( 'Hide on Mobile', 'my-plugin' ) }
checked={ hideOnMobile }
onChange={ ( val ) => setAttributes( { hideOnMobile: val } ) }
/>
</PanelBody>
</InspectorControls>
<div { ...useBlockProps( { style: blockStyle } ) }>
<RichText
tagName="p"
value={ content }
onChange={ ( val ) => setAttributes( { content: val } ) }
placeholder={ __( 'Write something…', 'my-plugin' ) }
/>
</div>
</>
);
}save.js
import { useBlockProps, RichText } from '@wordpress/block-editor';
export default function Save( { attributes } ) {
const {
content,
paddingDesktop, paddingTablet, paddingMobile,
fontSizeDesktop, fontSizeTablet, fontSizeMobile,
hideOnMobile,
} = attributes;
return (
<div
{ ...useBlockProps.save() }
data-hide-mobile={ hideOnMobile ? 'true' : undefined }
style={ {
'--padding-desktop': `${ paddingDesktop }px`,
'--padding-tablet': `${ paddingTablet }px`,
'--padding-mobile': `${ paddingMobile }px`,
'--font-size-desktop': `${ fontSizeDesktop }px`,
'--font-size-tablet': `${ fontSizeTablet }px`,
'--font-size-mobile': `${ fontSizeMobile }px`,
} }
>
<RichText.Content tagName="p" value={ content } />
</div>
);
}style.css (frontend)
.wp-block-my-plugin-my-block {
padding: var(--padding-desktop);
font-size: var(--font-size-desktop);
}
@media ( max-width: 1024px ) {
.wp-block-my-plugin-my-block {
padding: var(--padding-tablet);
font-size: var(--font-size-tablet);
}
}
@media ( max-width: 767px ) {
.wp-block-my-plugin-my-block {
padding: var(--padding-mobile);
font-size: var(--font-size-mobile);
}
.wp-block-my-plugin-my-block[data-hide-mobile="true"] {
display: none;
}
}License
GPL-2.0-or-later
