npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gutenberg-responsive-control

v1.0.1

Published

A responsive device control component for WordPress Gutenberg blocks

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 setAttributes or 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 resMode attribute — pass setAttributes only when you want to store the device type as a block attribute.
  • Two APIs — a ready-made <ResponsiveControl> component or a headless useDeviceType() hook for custom UIs.

Table of Contents


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-control

CSS 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 device

Use 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 sync

The 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