Skip to content

Editor integration ​

Expose theme tokens, breakpoints, and custom icons to the editor.

Provide Tailwind-like config and breakpoints ​

ts
import { addFilter } from '@wordpress/hooks';

import webentorConfig, { customTypographyKeys } from '../../webentor-config';

addFilter('webentor.core.twBreakpoints', 'theme/bp', (bps) => {
  for (const key in webentorConfig.theme.screens) {
    if (!bps.includes(key)) bps.push(key);
  }
  return bps;
});

addFilter('webentor.core.twTheme', 'theme/tw', () => webentorConfig.theme);

addFilter('webentor.core.customTypographyKeys', 'theme/typo', () => [
  { key: 'none', name: 'None', value: '', __experimentalHint: 'Inherit' },
  ...customTypographyKeys.map((i) => ({
    key: i.key,
    name: i.key,
    value: i.key,
    __experimentalHint: i.size,
  })),
]);

WordPress viewports follow the Tailwind breakpoints ​

WordPress 7.1 adds its own per-viewport styling (View menu → Responsive styles, then Tablet / Mobile) for core block supports such as text alignment and colors. It uses two breakpoints from theme.json settings.viewport, which default to 480px and 782px — cutting straight through the sm and md ranges.

Core derives settings.viewport from settings.custom.breakpoints so the two systems agree on where a range ends:

WordPress viewportMedia queryWebentor range
Mobilewidth <= md - 1pxbasic, sm
Tabletmd - 1px < width <= lg - 1pxmd
Desktopwidth > lg - 1pxlg and up

With the starter breakpoints that is mobile: 767px, tablet: 991px. Declaring settings.viewport in theme.json yourself takes precedence, and the PHP filter webentor/theme_json_viewport can adjust the derived values.

The two systems stay separate on purpose. WordPress viewport styles are desktop-first overrides emitted as !important CSS for core supports; Webentor responsive settings are mobile-first Tailwind classes. While a WordPress viewport is selected the block inspector shows only WordPress style panels — every Settings-tab control disappears, WordPress's own included. Switch back to Desktop to edit Webentor settings.

Customize quick layout presets ​

Register the preset filter in the same editor bootstrap where you already wire breakpoints and theme tokens. Core starts with an empty preset array, so the normal pattern is to return a fresh list for the blocks your theme supports:

ts
addFilter(
  'webentor.core.responsiveSettings.layoutPresets',
  'theme/responsive/presets',
  (_presets, blockName, twTheme) => {
    if (blockName === 'webentor/l-flexible-container') {
      return [
        {
          id: 'content-stack',
          label: 'Content Stack',
          description: 'Vertical flex stack with theme spacing',
          applies: {
            layout: {
              display: { value: { basic: 'flex' } },
            },
            flexbox: {
              'flex-direction': { value: { basic: 'flex-col' } },
              gap: {
                value: {
                  basic: 'gap-8',
                },
              },
            },
          },
        },
        {
          id: 'two-col-grid',
          label: '2 Column Grid',
          description: 'Simple grid for equal-width content',
          applies: {
            layout: {
              display: { value: { basic: 'grid' } },
            },
            grid: {
              'grid-cols': { value: { basic: 'grid-cols-2' } },
              gap: { value: { basic: 'gap-6' } },
            },
          },
        },
      ];
    }

    return [];
  },
);

Returning [] hides the preset UI for blocks that do not have a theme-defined catalog. If you share presets across multiple blocks, extract a helper and return that helper result from the filter.

When a preset needs classes that are not generated by the standard responsive settings modules, add them with customClasses and make sure they exist in theme CSS and in your safelist when the build cannot discover them statically:

ts
addFilter(
  'webentor.core.responsiveSettings.layoutPresets',
  'theme/responsive/custom-classes',
  (_presets, blockName) => {
    if (blockName !== 'webentor/l-flexible-container') {
      return [];
    }

    return [
      {
        id: 'flex-3-col-wrap',
        label: '3 Column Wrap',
        description: 'Wraps into equal-width columns with custom utilities',
        applies: {
          layout: {
            display: { value: { basic: 'flex' } },
          },
          flexbox: {
            'flex-wrap': { value: { basic: 'flex-wrap' } },
          },
        },
        customClasses: ['w-flex-cols', 'w-flex-cols-3', 'w-gap-4'],
      },
    ];
  },
);

Register custom icons for Icon Picker ​

ts
import { registerIcons } from '@10up/block-components';

const svgFiles = import.meta.glob('../../images/svg/*.svg', {
  eager: true,
  query: '?raw',
  import: 'default',
});
const icons = Object.keys(svgFiles).map((p) => {
  const name = p.split('/').pop().replace('.svg', '');
  return { source: svgFiles[p] as string, name, label: name };
});

registerIcons({ name: 'webentor', label: 'Webentor', icons });

Auto-import block editors ​

ts
// resources/scripts/editor.ts
import './blocks-filters/_core-init';
import './blocks-filters/_register-icons';

import.meta.glob('../blocks/**/*.block.{ts,tsx}', { eager: true });