This is the full developer documentation for @isudev/gutenberg # @isudev/gutenberg > Standalone components, controls, fields and hooks for the WordPress Gutenberg editor. Components, controls, fields and hooks for the WordPress Gutenberg editor — the parts of a block’s editor UI that would otherwise be rewritten per project. Targets **WordPress 7.0**, ships ESM with type declarations, and has nothing but `@wordpress/*` and React at runtime. ```bash npm install @isudev/gutenberg ``` ## Importing modules [Section titled “Importing modules”](#importing-modules) Prefer the narrowest public subpath. It bypasses the category barrel and gives your bundler the smallest and most explicit module graph: ```js import { MediaControl } from '@isudev/gutenberg/controls/MediaControl'; import { useBreakpoint } from '@isudev/gutenberg/hooks/useBreakpoint'; ``` Category imports are convenient when several related modules are used together, and stay tree-shakeable in production ESM builds: ```js import { BlockLinkControl, LinkText } from '@isudev/gutenberg/controls'; ``` Never import from `dist/` — only the documented subpaths are public API. ## ESM only [Section titled “ESM only”](#esm-only) The package ships ESM exclusively and defines its public surface with `exports` alone, so a TypeScript consumer needs `"moduleResolution": "bundler"` (or `"node16"`/`"nodenext"`) to see its types — the legacy `"node"` strategy reads only `main` and finds nothing. From CommonJS, use `await import( … )` instead of `require()`. `@wordpress/scripts` needs no configuration for either. ## Where to start [Section titled “Where to start”](#where-to-start) * **[Reference](/reference/controls/)** — every public component, control, field and hook, with all of its props and runnable examples. Each page is generated from the README that ships beside that module’s source. * **[Guide for coding agents](/agents/)** — the module catalog and the rules that are easy to get wrong, in the form shipped inside the npm tarball. ## For AI coding agents [Section titled “For AI coding agents”](#for-ai-coding-agents) This site publishes [`/llms.txt`](/llms.txt), [`/llms-full.txt`](/llms-full.txt) and [`/llms-small.txt`](/llms-small.txt), plus the machine-readable module catalog at [`/catalog.json`](/catalog.json). In a project that already depends on the library, run: ```bash npx @isudev/gutenberg init ``` That vendors the module catalog into `.agents/vendor/` and points your `AGENTS.md` — and your Cursor rules or Copilot instructions, if you use them — at it, so your agent finds the documentation without being told about it every time. # Guide for coding agents > The module catalog and usage rules shipped inside the npm tarball as AGENTS.md. You are writing WordPress block code that consumes this library. This file is the index: skim the catalog at the bottom, then open the `Full docs` README for the specific module you are about to use. Those READMEs document every prop, default and behaviour; this file deliberately does not repeat them. This is the consumer-facing guide shipped inside the npm tarball. If you are working *on* the library itself, the contributor guide is `AGENTS.md` at the repository root instead. ## What this library is [Section titled “What this library is”](#what-this-library-is) Components, controls, fields and hooks for the WordPress Gutenberg editor — the parts of a block’s editor UI that would otherwise be rewritten per project. It targets **WordPress 7.0** and ships ESM with type declarations. ## Import rules [Section titled “Import rules”](#import-rules) Prefer the narrowest subpath. It bypasses the category barrel and gives the consumer’s bundler the smallest module graph: ```js import { MediaControl } from '@isudev/gutenberg/controls/MediaControl'; import { useBreakpoint } from '@isudev/gutenberg/hooks/useBreakpoint'; ``` The category barrel is fine when several related modules are used together and stays tree-shakeable in production ESM builds: ```js import { BlockLinkControl, LinkText } from '@isudev/gutenberg/controls'; ``` * **Never import from `dist/`.** Only the subpaths in the catalog below are public API. * **Never import from `_internal/`.** It is private and not exported. * The root entry (`@isudev/gutenberg`) works but says less about intent — avoid it in reusable block code. ## Rules that are easy to get wrong [Section titled “Rules that are easy to get wrong”](#rules-that-are-easy-to-get-wrong) 1. **Nothing is read from a global registry.** Icons, option lists and configuration are passed in as props. If you are looking for a place to register icons globally, there isn’t one, and adding a module-level registry defeats the design. Pass a collection. 2. **Where a field reads its options is separate from where it reads and writes its value.** `optionsSource` and `valueBinding` are independent; do not assume a taxonomy options source implies a taxonomy value binding. 3. **Two modes, pick the smaller one.** Easy mode (`MetaSelectControl`, `TaxonomySelectControl`) takes one key and covers the common case. Advanced mode (`SelectField`, `RadioField`) composes `optionsSource` + `valueBinding` and is for the cases easy mode cannot express. Do not reach for advanced mode by default. 4. **Media editing is modular.** `MediaControl` composes the canvas, toolbar and sidebar surfaces with per-location switches. Use the individual controls only when you need one surface without the others. 5. **Responsive values come from the breakpoint kernel.** `ResponsiveControl` wires the switcher, the selection state and the attribute plumbing together — compose `useBreakpoint` + `useResponsiveAttribute` + `BreakpointSwitcher` by hand only when you need a layout `ResponsiveControl` cannot render. ## Build assumptions [Section titled “Build assumptions”](#build-assumptions) `@wordpress/*`, `react`, `react-dom` and `react/jsx-runtime` are peer dependencies and stay external — the consumer’s build resolves them to the WordPress-provided globals, so there is exactly one copy at runtime. `@wordpress/scripts` does this out of the box; a custom build needs `DependencyExtractionWebpackPlugin` or an equivalent externals configuration. The package is **ESM only** and its public surface is `exports` alone — no `main`, no CommonJS build. If you are writing or fixing a consumer’s `tsconfig.json`, it needs `"moduleResolution": "bundler"` (or `"node16"`/`"nodenext"`); the legacy `"node"` strategy reads only `main` and will silently resolve no types at all. From CommonJS, use `await import( … )` rather than `require()`. Blocks using this library should be `"apiVersion": 3` in `block.json`. WordPress 7.1 iframes the post editor unconditionally, and `apiVersion 2` blocks stop working there. ## Module catalog [Section titled “Module catalog”](#module-catalog) `@isudev/gutenberg@0.1.0` — 28 public modules. Read the listed `Full docs` file before using a module; it documents every prop, behaviour and example. The paths are relative to the package root, so from a consumer project they resolve as `node_modules/@isudev/gutenberg/`. ### Components [Section titled “Components”](#components) | Module | What it does | Narrowest import | Props | Full docs | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ----- | ------------------------------------------------- | | `BreakpointSwitcher` | Switches which breakpoint a responsive setting is being edited for, as an always-visible row of icons or a compact dropdown. | `@isudev/gutenberg/components/BreakpointSwitcher` | 8 | `src/components/BreakpointSwitcher/README.md` | | `ColorPopup` | A color swatch button that opens a popover with `ColorPalette`, and reports back the full color object (`{ color, name, slug }`), not just the hex string `ColorPalette` gives you. | `@isudev/gutenberg/components/ColorPopup` | 9 | `src/components/ColorPopup/README.md` | | `Icon` | Renders one named icon from an injected collection. Empty and unknown names render nothing. The folder also exports collection resolution and the explicit `wp_localize_script` adapter shared by `IconPicker` and `IconSelect`. | `@isudev/gutenberg/components/Icon` | 7 | `src/components/Icon/README.md` | | `IconPicker` | Displays an accessible grid of named icons with optional search and clearing. It is the always-visible selection surface used by `IconSelect`. | `@isudev/gutenberg/components/IconPicker` | 15 | `src/components/IconPicker/README.md` | | `IconSelect` | Shows the current icon and label in a compact WordPress button. Clicking it opens `IconPicker` in a popover; with no selected value, no icon preview is rendered. | `@isudev/gutenberg/components/IconSelect` | 20 | `src/components/IconSelect/README.md` | | `MediaFocalPointControl` | A standalone wrapper around WordPress’ `FocalPointPicker` for a serializable image or video value. It can be imported without any media modal, toolbar or inspector controls. | `@isudev/gutenberg/components/MediaFocalPointControl` | 10 | `src/components/MediaFocalPointControl/README.md` | | `MediaPreview` | Renders a serializable `MediaValue` as an image or video. It is props-only, performs no REST requests, and maps an optional focal point to safe CSS `object-position` values. | `@isudev/gutenberg/components/MediaPreview` | 12 | `src/components/MediaPreview/README.md` | ### Controls [Section titled “Controls”](#controls) | Module | What it does | Narrowest import | Props | Full docs | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----- | -------------------------------------------- | | `BlockLinkControl` | Injects an add/edit action and an optional unlink action into Gutenberg’s `BlockControls`. The add/edit action opens the same `LinkPickerControl` used by the lower-level and editable-text link APIs. | `@isudev/gutenberg/controls/BlockLinkControl` | 14 | `src/controls/BlockLinkControl/README.md` | | `LinkPickerControl` | Adds WordPress’ native link picker to any consumer-rendered element. It owns popover state and link normalization, while a render prop keeps the trigger and the block’s markup under the consumer’s control. | `@isudev/gutenberg/controls/LinkPickerControl` | 28 | `src/controls/LinkPickerControl/README.md` | | `LinkText` | Provides editable `RichText` rendered as an anchor and a native-style link action in the inline block toolbar. It is the ready-made path for CTA labels, inline links and lists of editable links. | `@isudev/gutenberg/controls/LinkText` | 19 | `src/controls/LinkText/README.md` | | `MediaCanvasControl` | Renders a media placeholder before selection and an image/video preview with compact replace/remove actions afterward. Each action can be disabled independently. | `@isudev/gutenberg/controls/MediaCanvasControl` | 15 | `src/controls/MediaCanvasControl/README.md` | | `MediaControl` | The complete single-media editor composed from `MediaCanvasControl`, `MediaToolbarControl` and `MediaSidebarControl`. Every location can be disabled, and each location independently controls its select, replace and remove actions. | `@isudev/gutenberg/controls/MediaControl` | 13 | `src/controls/MediaControl/README.md` | | `MediaPickerControl` | Connects any consumer-rendered trigger to WordPress’ native media modal. A render prop exposes `open`, selection state and the current select/replace action, while selections are normalized to a small serializable `MediaValue`. | `@isudev/gutenberg/controls/MediaPickerControl` | 10 | `src/controls/MediaPickerControl/README.md` | | `MediaSidebarControl` | Adds a media panel to `InspectorControls` with independently configurable actions and one of three preview modes: static media, interactive focal point, or no preview. | `@isudev/gutenberg/controls/MediaSidebarControl` | 17 | `src/controls/MediaSidebarControl/README.md` | | `MediaSourceControl` | Provides the native image-block source workflow as either inline placeholder buttons or a replacement dropdown: media library, upload, direct URL, current post featured image and drag-and-drop. Every source is independently configurable. | `@isudev/gutenberg/controls/MediaSourceControl` | 18 | `src/controls/MediaSourceControl/README.md` | | `MediaToolbarControl` | Adds state-aware select/replace and remove actions to Gutenberg’s block toolbar without rendering any block content or inspector UI. | `@isudev/gutenberg/controls/MediaToolbarControl` | 11 | `src/controls/MediaToolbarControl/README.md` | | `ResponsiveControl` | Makes any control responsive: renders a label and a breakpoint switcher, then hands the resolved per-breakpoint value to a render prop. | `@isudev/gutenberg/controls/ResponsiveControl` | 11 | `src/controls/ResponsiveControl/README.md` | ### Fields (advanced mode) [Section titled “Fields (advanced mode)”](#fields-advanced-mode) | Module | What it does | Narrowest import | Props | Full docs | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ----- | ---------------------------------- | | `RadioField` | A radio-button field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. | `@isudev/gutenberg/fields/RadioField` | 8 | `src/fields/RadioField/README.md` | | `SelectField` | A select field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. | `@isudev/gutenberg/fields/SelectField` | 8 | `src/fields/SelectField/README.md` | ### Post meta (easy mode) [Section titled “Post meta (easy mode)”](#post-meta-easy-mode) | Module | What it does | Narrowest import | Props | Full docs | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ----- | -------------------------------------- | | `MetaRadioControl` | A radio group bound to a single post meta value — pass the meta key, get a working `RadioControl` that reads and writes it, with the options coming from wherever you like. | `@isudev/gutenberg/meta/MetaRadioControl` | 9 | `src/meta/MetaRadioControl/README.md` | | `MetaSelectControl` | A dropdown bound to a single post meta value — pass the meta key, get a working `SelectControl` that reads and writes it, with the options coming from wherever you like. | `@isudev/gutenberg/meta/MetaSelectControl` | 9 | `src/meta/MetaSelectControl/README.md` | ### Taxonomy (easy mode) [Section titled “Taxonomy (easy mode)”](#taxonomy-easy-mode) | Module | What it does | Narrowest import | Props | Full docs | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ----- | ---------------------------------------------- | | `TaxonomySelectControl` | A dropdown whose options are a taxonomy’s terms and whose value is that same taxonomy’s terms on the current post — pass the taxonomy name, get a working single-term picker. | `@isudev/gutenberg/taxonomy/TaxonomySelectControl` | 8 | `src/taxonomy/TaxonomySelectControl/README.md` | ### Hooks [Section titled “Hooks”](#hooks) | Module | What it does | Narrowest import | Props | Full docs | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ----- | -------------------------------------------- | | `useBreakpoint` | Owns which breakpoint a responsive setting is currently being edited for, with optional two-way sync to the editor’s device preview. | `@isudev/gutenberg/hooks/useBreakpoint` | 4 | `src/hooks/useBreakpoint/README.md` | | `useCurrentPostId` | Returns the ID of the post currently open in the editor. | `@isudev/gutenberg/hooks/useCurrentPostId` | 0 | `src/hooks/useCurrentPostId/README.md` | | `useCurrentPostType` | Returns the post type of the post currently open in the editor. | `@isudev/gutenberg/hooks/useCurrentPostType` | 0 | `src/hooks/useCurrentPostType/README.md` | | `useDebouncedValue` | Returns a debounced copy of a value that only updates after a delay of no further changes. | `@isudev/gutenberg/hooks/useDebouncedValue` | 2 | `src/hooks/useDebouncedValue/README.md` | | `usePrevious` | Returns the value a component held on its previous committed render. | `@isudev/gutenberg/hooks/usePrevious` | 1 | `src/hooks/usePrevious/README.md` | | `useResponsiveAttribute` | Reads and writes one logical setting across a breakpoint set, resolving the cascade so a control always has the right own value, inherited value and override state for whichever breakpoint is active. | `@isudev/gutenberg/hooks/useResponsiveAttribute` | 5 | `src/hooks/useResponsiveAttribute/README.md` | *** # Components > Pure, props-only UI for breakpoints, colors, icons and media previews. ## Modules [Section titled “Modules”](#modules) | Module | What it does | Narrowest import | | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | [`BreakpointSwitcher`](/reference/components/breakpoint-switcher/) | Switches which breakpoint a responsive setting is being edited for, as an always-visible row of icons or a compact dropdown. | `@isudev/gutenberg/components/BreakpointSwitcher` | | [`ColorPopup`](/reference/components/color-popup/) | A color swatch button that opens a popover with `ColorPalette`, and reports back the full color object (`{ color, name, slug }`), not just the hex string `ColorPalette` gives you. | `@isudev/gutenberg/components/ColorPopup` | | [`Icon`](/reference/components/icon/) | Renders one named icon from an injected collection. Empty and unknown names render nothing. The folder also exports collection resolution and the explicit `wp_localize_script` adapter shared by `IconPicker` and `IconSelect`. | `@isudev/gutenberg/components/Icon` | | [`IconPicker`](/reference/components/icon-picker/) | Displays an accessible grid of named icons with optional search and clearing. It is the always-visible selection surface used by `IconSelect`. | `@isudev/gutenberg/components/IconPicker` | | [`IconSelect`](/reference/components/icon-select/) | Shows the current icon and label in a compact WordPress button. Clicking it opens `IconPicker` in a popover; with no selected value, no icon preview is rendered. | `@isudev/gutenberg/components/IconSelect` | | [`MediaFocalPointControl`](/reference/components/media-focal-point-control/) | A standalone wrapper around WordPress’ `FocalPointPicker` for a serializable image or video value. It can be imported without any media modal, toolbar or inspector controls. | `@isudev/gutenberg/components/MediaFocalPointControl` | | [`MediaPreview`](/reference/components/media-preview/) | Renders a serializable `MediaValue` as an image or video. It is props-only, performs no REST requests, and maps an optional focal point to safe CSS `object-position` values. | `@isudev/gutenberg/components/MediaPreview` | # BreakpointSwitcher > Switches which breakpoint a responsive setting is being edited for, as an always-visible row of icons or a compact dropdown. ## Summary [Section titled “Summary”](#summary) Switches which breakpoint a responsive setting is being edited for, as an always-visible row of icons or a compact dropdown. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when a block setting needs a different value per breakpoint and you want the author to see which breakpoints carry an override. Do not use it to preview the site at a device size — that is the editor’s own device preview. `useBreakpoint`’s `syncToEditor` connects the two if you want them linked. Do not reach for this component alone if you also need the values: `ResponsiveControl` wires the switcher, the selection state and the attribute plumbing together. ## Import [Section titled “Import”](#import) ```js import { BreakpointSwitcher } from '@isudev/gutenberg/components'; // or, skipping the barrel: import { BreakpointSwitcher } from '@isudev/gutenberg/components/BreakpointSwitcher'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | --------------------- | ------------------------- | --------------------- | -------- | ----------------------------------------------------------------------- | | `value` | `string` | — | Yes | Currently selected breakpoint id. | | `onChange` | `( id: string ) => void` | — | Yes | Called with the newly selected breakpoint id. | | `variant` | `'inline' \| 'dropdown'` | `'inline'` | No | Always-visible row, or a button that opens a menu. | | `breakpoints` | `Breakpoint[]` | `DEFAULT_BREAKPOINTS` | No | The breakpoint set to offer. | | `hasValue` | `Record` | `{}` | No | Which breakpoints carry an override, keyed by id. Drives the indicator. | | `label` | `string` | `'Breakpoint'` | No | Accessible name for the group or dropdown toggle. | | `hideLabelFromVision` | `boolean` | `false` | No | Show the label to screen readers only. Inline variant only. | | `className` | `string` | `undefined` | No | Extra class name on the root element. | ## Examples [Section titled “Examples”](#examples) ### Standalone, controlled [Section titled “Standalone, controlled”](#standalone-controlled) ```jsx const [ breakpoint, setBreakpoint ] = useState( 'desktop' ); ``` ### Compact dropdown with override indicators [Section titled “Compact dropdown with override indicators”](#compact-dropdown-with-override-indicators) ```jsx ``` ### A custom breakpoint set [Section titled “A custom breakpoint set”](#a-custom-breakpoint-set) ```jsx import { DEFAULT_BREAKPOINTS } from '@isudev/gutenberg/breakpoints'; import { desktop } from '@wordpress/icons'; const BREAKPOINTS = [ ...DEFAULT_BREAKPOINTS, { id: 'wide', label: 'Wide', icon: desktop, suffix: 'Wide' }, ]; ``` ## Behavior [Section titled “Behavior”](#behavior) * Renders `null` when fewer than two breakpoints are configured — a one-option switcher is noise. * Fully controlled. It holds no state, reads no store, and knows nothing about block attributes. * The inline variant is built on `ToggleGroupControl`, so arrow keys move between options and focus is managed for you. The dropdown variant is built on `DropdownMenu`, which handles outside-click, focus return and `Escape`. * An overridden breakpoint gains `(modified)` in its accessible name in both variants, and in the inline variant its icon also carries a dot. The base breakpoint never shows either: it is not an override, it is the value being overridden. * An invalid `breakpoints` set warns once in development and falls back to `DEFAULT_BREAKPOINTS`. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Both variants inherit editor chrome from `@wordpress/components`. The override dot is an inline style using `var(--wp-admin-theme-color, #3858e9)`. Tint icons with the CSS `color` property, not `fill` — `@wordpress/icons` v15 switched to `fill="currentColor"`. ## Gotchas [Section titled “Gotchas”](#gotchas) * Cascade direction follows the **order** of the `breakpoints` array, and nothing validates that the order is sensible. List them from base outwards, widest to narrowest for a desktop-first set. * `hasValue` is not computed here. Pass the map from `useResponsiveAttribute`, or the indicator will never appear. * `hideLabelFromVision` affects the inline variant only; the dropdown’s label is always the toggle’s accessible name and is never rendered as text. ## Related [Section titled “Related”](#related) * [`useBreakpoint`](/reference/hooks/) — selection state and editor sync. * [`useResponsiveAttribute`](/reference/hooks/) — per-breakpoint values. * [`ResponsiveControl`](/reference/controls/responsive-control/) — all three wired up. # ColorPopup > A color swatch button that opens a popover with `ColorPalette`, and reports back the full color object (`{ color, name, slug }`), not just the hex string `ColorPalette` gives you. ## Summary [Section titled “Summary”](#summary) A color swatch button that opens a popover with `ColorPalette`, and reports back the full color object (`{ color, name, slug }`), not just the hex string `ColorPalette` gives you. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it inside `InspectorControls` wherever a setting needs a color and you want to persist the palette slug alongside the value, so the frontend can react to a theme.json palette change instead of a frozen hex string. Do not use it if a bare hex value is all you need — reach for `ColorPalette` directly. Do not expect it to read a theme’s color palette on its own: `colors` is a prop, never fetched from a store (see decision 0001) — pass the result of `useSettings( 'color.palette.theme' )` or similar from the caller. ## Import [Section titled “Import”](#import) ```js import { ColorPopup } from '@isudev/gutenberg/components'; // or, skipping the barrel: import { ColorPopup } from '@isudev/gutenberg/components/ColorPopup'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------- | ------------------------------------------------------ | ---------------- | -------- | ---------------------------------------------------------------- | | `label` | `string` | — | Yes | Text next to the color swatch on the toggle button. | | `value` | `string` | — | Yes | Currently selected color or slug. Empty string for no selection. | | `onChange` | `( color: ColorPopupColor ) => void` | — | Yes | Called with the full resolved color object, never a bare string. | | `colors` | `Array<{ color: string; name: string; slug: string }>` | `[]` | No | Palette offered in the popup. | | `enableAlpha` | `boolean` | `false` | No | Adds an opacity slider to the popup. | | `alpha` | `number` | `1` | No | Current alpha, 0–1. Only meaningful when `enableAlpha` is set. | | `popupLabel` | `string` | `'Select Color'` | No | Heading shown inside the popup, above the palette. | | `clearable` | `boolean` | `false` | No | Shows a button that resets the value to empty. | | `className` | `string` | `undefined` | No | Extra class name on the toggle button. | `ColorPopupColor` is `{ color: string; name: string; slug: string; alpha?: number }`. `name` and `slug` are empty strings for a color typed or picked outside the given `colors`. ## Examples [Section titled “Examples”](#examples) ### Minimal [Section titled “Minimal”](#minimal) ```jsx const [ value, setValue ] = useState( '' ); setValue( color.color ) } colors={ [ { color: '#111111', name: 'Contrast', slug: 'contrast' }, { color: '#ffffff', name: 'Base', slug: 'base' }, ] } /> ``` ### Persisting the slug, driven by the theme palette [Section titled “Persisting the slug, driven by the theme palette”](#persisting-the-slug-driven-by-the-theme-palette) ```jsx const [ palette ] = useSettings( 'color.palette.theme' ); setAttributes( { backgroundColor: color.slug || color.color } ) } /> ``` ### With opacity [Section titled “With opacity”](#with-opacity) ```jsx setAttributes( { overlayColor: color.slug || color.color, overlayAlpha: color.alpha, } ) } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * `value` may be either a hex/rgb string or a palette `slug` — both are matched against `colors` so a saved slug still resolves correctly after a hex value elsewhere. * A value that matches nothing in `colors` is treated as a custom color: `onChange` receives it with empty `name` and `slug`. * Changing the alpha slider is a no-op while `value` is empty — there is no color to attach an opacity to. * The clear button (`clearable`) is disabled while `value` is already empty. ## Styling [Section titled “Styling”](#styling) No stylesheet ships; the toggle button’s swatch uses inline styles. `sideEffects: false` on the package holds. ## Gotchas [Section titled “Gotchas”](#gotchas) * `onChange` always receives an object, never a string — do not treat it like `ColorPalette`’s own `onChange`. * This component does not read a theme.json palette by itself. Pass `colors={ [] }` (the default) and only the custom-color picker will show. ## Related [Section titled “Related”](#related) * `@wordpress/components` `ColorPalette` — the lower-level primitive this wraps. # Icon > Renders one named icon from an injected collection. Empty and unknown names render nothing. The folder also exports collection resolution and the explicit `wp_localize_script` adapter shared by `IconPicker` and `IconSelect`. ## Summary [Section titled “Summary”](#summary) Renders one named icon from an injected collection. Empty and unknown names render nothing. The folder also exports collection resolution and the explicit `wp_localize_script` adapter shared by `IconPicker` and `IconSelect`. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use `Icon` to display a value already selected and stored by a block. Use `IconPicker` for an always-visible grid or `IconSelect` for the complete button-and-popover interaction. The component never reads a global registry by itself. Call `getLocalizedIcons()` at the integration boundary and pass its result through `defaultIcons`. ## Import [Section titled “Import”](#import) ```js import { Icon, getLocalizedIcons } from '@isudev/gutenberg/components'; ``` Or import the single component and its helpers: ```js import { Icon, getLocalizedIcons, } from '@isudev/gutenberg/components/Icon'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | -------------- | --------------------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------ | | `defaultIcons` | `readonly IconDefinition[]` | `[]` | No | Base registry, commonly returned by `getLocalizedIcons()`. | | `icons` | `readonly IconChoice[]` | `undefined` | No | Complete ordered override. Strings select names from `defaultIcons`; definitions replace the defaults. | | `name` | `string` | `undefined` | No | Selected icon name. Empty and unknown names render nothing. | | `size` | `number` | `24` | No | Rendered width and height in pixels. | | `label` | `string` | `undefined` | No | Accessible label. Omit for a decorative icon. | | `className` | `string` | `undefined` | No | Extra class on the icon wrapper. | | `style` | `CSSProperties` | `undefined` | No | Inline styles on the icon wrapper. | ## Examples [Section titled “Examples”](#examples) ### Localized WordPress icons [Section titled “Localized WordPress icons”](#localized-wordpress-icons) Localize data on the block or plugin script that consumes it, not on a WordPress Core handle: ```php $handle = generate_block_asset_handle( 'my-plugin/icon-block', 'editorScript' ); wp_localize_script( $handle, 'isudevIcons', [ [ 'name' => 'alert', 'label' => __( 'Alert', 'my-plugin' ), 'icon' => '', ], [ 'name' => 'arrow-right', // label falls back to name. 'icon' => plugins_url( 'assets/arrow-right.svg', __FILE__ ), ], ] ); ``` Read the global once and inject it: ```jsx const defaultIcons = getLocalizedIcons(); ``` Pass another global name when needed: ```js const icons = getLocalizedIcons( 'myPluginIcons' ); ``` ### Direct definitions and name subsets [Section titled “Direct definitions and name subsets”](#direct-definitions-and-name-subsets) ```jsx const icons = [ { name: 'alert', label: 'Alert', icon: alertIcon }, { name: 'calendar', label: 'Calendar', icon: calendarIcon }, ]; ``` A string array is not a second registry. It selects and orders entries from `defaultIcons`: ```jsx ``` ## Behavior [Section titled “Behavior”](#behavior) * `IconDefinition` has `name`, optional `label`, `icon` and optional `keywords`. Missing labels normalize to `name`. * `icon` accepts a WordPress `IconType`, a Dashicon name, an image URL or serialized SVG. * Serialized SVG is percent-encoded and rendered through ``; it is never injected with `dangerouslySetInnerHTML`. * URL-like strings use ``. Other string values go through WordPress’ `Icon`, allowing Dashicon names. * `parseLocalizedIcons()` drops malformed values and non-string graphics because localized data must remain JSON-compatible. * `getLocalizedIcons()` reads `globalThis.isudevIcons` by default and returns an empty array when the global is unavailable. Components themselves do not touch global state. * When `icons` is omitted, all `defaultIcons` are available. Once supplied, `icons` is the complete collection: unknown names and duplicate names are omitted. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. The wrapper is an inline flex box sized by `size`; use `className` and `style` for context-specific presentation. ## Gotchas [Section titled “Gotchas”](#gotchas) * Localized SVG and URLs are configuration, not user-authored content. Sanitize server-side data before localizing it and restrict who can modify the registry. * Localize on your own registered script handle. Attaching application data to `wp-blocks` couples it to Core’s loading lifecycle. * A name-only `icons` array needs `defaultIcons`; unknown names intentionally disappear. * Localized data cannot contain React elements or functions. Pass those directly as icon definitions in JavaScript. ## Related [Section titled “Related”](#related) * [`IconPicker`](/reference/components/icon-picker/) — visible icon grid. * [`IconSelect`](/reference/components/icon-select/) — selected preview with a dropdown picker. # IconPicker > Displays an accessible grid of named icons with optional search and clearing. It is the always-visible selection surface used by `IconSelect`. ## Summary [Section titled “Summary”](#summary) Displays an accessible grid of named icons with optional search and clearing. It is the always-visible selection surface used by `IconSelect`. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it inside an Inspector panel or a custom popover when the grid should remain visible. Use `IconSelect` for a ready-made compact trigger and popover, or `Icon` only to render a stored selection. ## Import [Section titled “Import”](#import) ```js import { IconPicker } from '@isudev/gutenberg/components'; ``` Or import the single component: ```js import { IconPicker } from '@isudev/gutenberg/components/IconPicker'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------- | --------------------------- | ------------------- | -------- | ----------------------------------------------------------- | | `defaultIcons` | `readonly IconDefinition[]` | `[]` | No | Base registry, commonly returned by `getLocalizedIcons()`. | | `icons` | `readonly IconChoice[]` | `undefined` | No | Complete ordered override or name subset of `defaultIcons`. | | `value` | `string` | `''` | No | Selected icon name. Empty string means no selection. | | `onChange` | `( name: string ) => void` | — | Yes | Receives the selected name or `''` when cleared. | | `label` | `string` | `undefined` | No | Optional picker heading and grid accessible label. | | `searchable` | `boolean` | `true` | No | Shows the search field. | | `searchLabel` | `string` | `'Search icons'` | No | Accessible search field label. | | `searchPlaceholder` | `string` | `'Search icons'` | No | Search input placeholder. | | `noResultsMessage` | `string` | `'No icons found.'` | No | Status shown for an empty collection or search result. | | `columns` | `number` | `6` | No | Number of grid columns; values below one become one. | | `iconSize` | `number` | `24` | No | Icon size inside each 40px selection button. | | `clearable` | `boolean` | `true` | No | Shows the clear-selection action. | | `clearLabel` | `string` | `'Clear icon'` | No | Clear action label. | | `className` | `string` | `undefined` | No | Extra class on the picker control. | | `style` | `CSSProperties` | `undefined` | No | Inline styles merged onto the picker content wrapper. | ## Examples [Section titled “Examples”](#examples) ### Localized registry [Section titled “Localized registry”](#localized-registry) ```jsx const defaultIcons = getLocalizedIcons(); setAttributes( { iconName } ) } /> ``` ### Restricted collection without search [Section titled “Restricted collection without search”](#restricted-collection-without-search) ```jsx setAttributes( { iconName } ) } searchable={ false } columns={ 3 } clearable={ false } /> ``` ### Fully custom icons [Section titled “Fully custom icons”](#fully-custom-icons) ```jsx ``` ## Behavior [Section titled “Behavior”](#behavior) * Search matches `name`, resolved `label` and `keywords`, case-insensitively. * Every icon is a real WordPress `Button` with an accessible label, tooltip and pressed state; the grid is keyboard reachable without hidden checkbox hacks. * Selection is controlled. The picker does not mutate or retain the selected value itself. * Clearing emits an empty string. The clear action remains focusable while disabled, following WordPress’ accessible disabled-control guidance. * `icons` follows the shared override rules documented by `Icon`. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. The layout uses an inline CSS grid and WordPress button styles. Use `className` and `style` for the surrounding control. ## Gotchas [Section titled “Gotchas”](#gotchas) * `searchable={ false }` is useful for very small curated sets; disabling it for a large registry makes selection unnecessarily slow. * The component does not virtualize the grid. Curate large registries with `icons` or enable search rather than sending thousands of graphics to one control. * `onChange( '' )` must be persisted as the block’s empty icon value. ## Related [Section titled “Related”](#related) * [`Icon`](/reference/components/icon/) — registry adapter and selected-icon renderer. * [`IconSelect`](/reference/components/icon-select/) — ready-made dropdown composition. # IconSelect > Shows the current icon and label in a compact WordPress button. Clicking it opens `IconPicker` in a popover; with no selected value, no icon preview is rendered. ## Summary [Section titled “Summary”](#summary) Shows the current icon and label in a compact WordPress button. Clicking it opens `IconPicker` in a popover; with no selected value, no icon preview is rendered. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it as the default icon control in Inspector panels or block content. Use `IconPicker` when the grid must always remain open, and `Icon` when selection is handled elsewhere. ## Import [Section titled “Import”](#import) ```js import { IconSelect } from '@isudev/gutenberg/components'; ``` Or import the single component: ```js import { IconSelect } from '@isudev/gutenberg/components/IconSelect'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------- | --------------------------- | ------------------- | -------- | --------------------------------------------------------------- | | `defaultIcons` | `readonly IconDefinition[]` | `[]` | No | Base registry, commonly returned by `getLocalizedIcons()`. | | `icons` | `readonly IconChoice[]` | `undefined` | No | Complete ordered override or name subset of `defaultIcons`. | | `value` | `string` | `''` | No | Selected icon name. Empty string means no selection. | | `onChange` | `( name: string ) => void` | — | Yes | Receives the selected name or `''` when cleared. | | `label` | `string` | — | Yes | Visible context label and accessible name of the select button. | | `placeholder` | `string` | `'Select icon'` | No | Text shown while no icon is selected. | | `pickerLabel` | `string` | `undefined` | No | Optional heading above the picker grid. | | `searchable` | `boolean` | `true` | No | Shows search inside the picker. | | `searchLabel` | `string` | `'Search icons'` | No | Accessible search field label. | | `searchPlaceholder` | `string` | `'Search icons'` | No | Search input placeholder. | | `noResultsMessage` | `string` | `'No icons found.'` | No | Empty-result status. | | `columns` | `number` | `6` | No | Number of picker grid columns. | | `iconSize` | `number` | `24` | No | Preview and grid icon size in pixels. | | `clearable` | `boolean` | `true` | No | Shows the clear-selection action. | | `clearLabel` | `string` | `'Clear icon'` | No | Clear action label. | | `closeOnSelect` | `boolean` | `true` | No | Closes after selecting or clearing an icon. | | `popoverPlacement` | `PopoverPlacement` | `'bottom-start'` | No | Popover placement relative to the select button. | | `className` | `string` | `undefined` | No | Extra class on the select button. | | `pickerClassName` | `string` | `undefined` | No | Extra class on the nested picker. | | `style` | `CSSProperties` | `undefined` | No | Inline styles on the select button. | ## Examples [Section titled “Examples”](#examples) ### Block attribute and localized defaults [Section titled “Block attribute and localized defaults”](#block-attribute-and-localized-defaults) ```jsx const defaultIcons = getLocalizedIcons(); setAttributes( { iconName } ) } /> ``` The same collection renders the saved icon: ```jsx ``` ### Curated, persistent picker [Section titled “Curated, persistent picker”](#curated-persistent-picker) ```jsx setAttributes( { iconName } ) } searchable={ false } closeOnSelect={ false } columns={ 3 } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * The button renders the selected icon and its resolved label. With no valid selection it renders only `placeholder`, never a fake fallback graphic. * The button exposes `aria-expanded`, `aria-haspopup` and a combined accessible name. * The popover uses WordPress `Dropdown`; it closes after selection by default. * Search, clear behavior and collection resolution are delegated to `IconPicker`, so both public selection surfaces behave identically. * `icons` follows the shared override rules documented by `Icon`. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. The trigger is a full-width WordPress secondary button; the popover content is 288px wide and uses the picker’s inline grid. Use `className`, `pickerClassName` and `style` for integration-specific adjustments. ## Gotchas [Section titled “Gotchas”](#gotchas) * The component stores only the selected name. Keep the registry stable across editor loads so a saved name can still be resolved. * When using localized data in a static block, the registry must be present while Gutenberg evaluates `save()` for block validation. * `closeOnSelect={ false }` is useful for exploration but requires the user to dismiss the popover manually. ## Related [Section titled “Related”](#related) * [`Icon`](/reference/components/icon/) — rendering and localized registry helpers. * [`IconPicker`](/reference/components/icon-picker/) — the underlying visible grid. # MediaFocalPointControl > A standalone wrapper around WordPress' `FocalPointPicker` for a serializable image or video value. It can be imported without any media modal, toolbar or inspector controls. ## Summary [Section titled “Summary”](#summary) A standalone wrapper around WordPress’ `FocalPointPicker` for a serializable image or video value. It can be imported without any media modal, toolbar or inspector controls. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it wherever focal-point editing is required independently. Use `MediaSidebarControl` with `preview="focal-point"` for a ready-made inspector panel, or `MediaControl` for the complete workflow. ## Import [Section titled “Import”](#import) ```js import { MediaFocalPointControl } from '@isudev/gutenberg/components'; import { MediaFocalPointControl } from '@isudev/gutenberg/components/MediaFocalPointControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | --------------------- | ------------------------------------------------- | --------------------- | -------- | ------------------------------------------ | | `media` | `MediaValue` | `{}` | No | Image or video displayed by the picker. | | `value` | `MediaFocalPoint` | `{ x: 0.5, y: 0.5 }` | No | Controlled normalized focal point. | | `onChange` | `( value: MediaFocalPoint \| undefined ) => void` | — | Yes | Receives changes and reset as `undefined`. | | `label` | `string` | `'Focal point'` | No | Visible picker label. | | `help` | `string` | `undefined` | No | Help text below the picker. | | `hideLabelFromVision` | `boolean` | `false` | No | Visually hides the accessible label. | | `autoPlay` | `boolean` | WordPress default | No | Controls video autoplay in the picker. | | `showReset` | `boolean` | `true` | No | Shows reset while a custom value exists. | | `resetLabel` | `string` | `'Reset focal point'` | No | Reset button label. | | `emptyFallback` | `ReactNode` | `null` | No | Rendered without a supported media URL. | ## Focal-point value [Section titled “Focal-point value”](#focal-point-value) `MediaFocalPoint` contains numeric `x` and `y` coordinates. Both use WordPress’ normalized `0`–`1` range: `{ x: 0, y: 0 }` is the top-left corner and `{ x: 1, y: 1 }` is the bottom-right corner. An `undefined` value represents the default center without persisting `{ x: 0.5, y: 0.5 }` to the block. ## Examples [Section titled “Examples”](#examples) ### Controlled focal point [Section titled “Controlled focal point”](#controlled-focal-point) ```jsx setAttributes( { focalPoint } ) } /> ``` ### Without reset UI [Section titled “Without reset UI”](#without-reset-ui) ```jsx setAttributes( { videoFocalPoint } ) } showReset={ false } hideLabelFromVision /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Supports image and video URLs only. * Undefined focal points display the center without writing a value. * Reset emits `undefined`, allowing the block attribute to return to its default. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet and uses WordPress component styles. ## Gotchas [Section titled “Gotchas”](#gotchas) The component is controlled. Update the value passed back by `onChange` or the picker will return to the previous point. ## Related [Section titled “Related”](#related) * [`MediaPreview`](/reference/components/media-preview/) * [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) # MediaPreview > Renders a serializable `MediaValue` as an image or video. It is props-only, performs no REST requests, and maps an optional focal point to safe CSS `object-position` values. ## Summary [Section titled “Summary”](#summary) Renders a serializable `MediaValue` as an image or video. It is props-only, performs no REST requests, and maps an optional focal point to safe CSS `object-position` values. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it for block and inspector previews when the media URL is already stored. Use `MediaPickerControl` to select media and `MediaControl` for the complete editor workflow. It intentionally does not render audio, documents or embeds; provide `unsupportedFallback` or a separate renderer for those formats. ## Import [Section titled “Import”](#import) ```js import { MediaPreview } from '@isudev/gutenberg/components'; import { MediaPreview } from '@isudev/gutenberg/components/MediaPreview'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | --------------------- | ------------------------------ | ----------- | -------- | --------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Serializable media URL, type and metadata. | | `focalPoint` | `MediaFocalPoint` | `undefined` | No | Normalized coordinates mapped to `object-position`. | | `aspectRatio` | `CSSProperties['aspectRatio']` | `undefined` | No | CSS aspect ratio of the media. | | `objectFit` | `CSSProperties['objectFit']` | `'cover'` | No | CSS object-fit mode. | | `width` | `CSSProperties['width']` | `'100%'` | No | CSS width of the media. | | `height` | `CSSProperties['height']` | `undefined` | No | CSS height of the media. | | `style` | `CSSProperties` | `undefined` | No | Additional media-element styles. | | `className` | `string` | `undefined` | No | Additional media-element class name. | | `imageProps` | `ImgHTMLAttributes` | `undefined` | No | Image-only props except controlled `src` and `alt`. | | `videoProps` | `VideoHTMLAttributes` | `undefined` | No | Video-only props except controlled `src`. | | `emptyFallback` | `ReactNode` | `null` | No | Rendered without a URL. | | `unsupportedFallback` | `ReactNode` | `null` | No | Rendered for non-image/video types. | ## Media value [Section titled “Media value”](#media-value) `value` is intentionally small enough to store directly in a block attribute: | Field | Type | Required | Description | | -------- | -------- | -------- | -------------------------------------------------------- | | `id` | `number` | No | WordPress attachment ID. | | `url` | `string` | No | URL rendered by the preview. | | `type` | `string` | No | Broad type; this component supports `image` and `video`. | | `mime` | `string` | No | MIME type such as `image/jpeg`. | | `alt` | `string` | No | Alternative text used for images. | | `width` | `number` | No | Selected rendition width in pixels. | | `height` | `number` | No | Selected rendition height in pixels. | `imageProps` accepts native image attributes except `src` and `alt`, which remain controlled by `value`. `videoProps` accepts native video attributes except `src`. ## Examples [Section titled “Examples”](#examples) ### Image with focal point [Section titled “Image with focal point”](#image-with-focal-point) ```jsx ``` ### Video preview [Section titled “Video preview”](#video-preview) ```jsx Preview unavailable.

} /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Missing URLs render `emptyFallback`; unsupported types render `unsupportedFallback`. * Images use `value.alt ?? ''`, so decorative or missing-alt previews remain valid. * Focal coordinates are clamped to 0–1 before conversion to percentages. * Videos render controls by default; `videoProps` can override that default. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Layout and media sizing are controlled by props and inline styles. ## Gotchas [Section titled “Gotchas”](#gotchas) Store `url` and `type` alongside an attachment `id`; this pure component does not hydrate attachment records from the editor store. ## Related [Section titled “Related”](#related) * [`MediaFocalPointControl`](/reference/components/media-focal-point-control/) * [`MediaCanvasControl`](/reference/controls/media-canvas-control/) * [`MediaControl`](/reference/controls/media-control/) # Controls > Editor UI: responsive, link and modular media editing surfaces. ## Modules [Section titled “Modules”](#modules) | Module | What it does | Narrowest import | | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | | [`BlockLinkControl`](/reference/controls/block-link-control/) | Injects an add/edit action and an optional unlink action into Gutenberg’s `BlockControls`. The add/edit action opens the same `LinkPickerControl` used by the lower-level and editable-text link APIs. | `@isudev/gutenberg/controls/BlockLinkControl` | | [`LinkPickerControl`](/reference/controls/link-picker-control/) | Adds WordPress’ native link picker to any consumer-rendered element. It owns popover state and link normalization, while a render prop keeps the trigger and the block’s markup under the consumer’s control. | `@isudev/gutenberg/controls/LinkPickerControl` | | [`LinkText`](/reference/controls/link-text/) | Provides editable `RichText` rendered as an anchor and a native-style link action in the inline block toolbar. It is the ready-made path for CTA labels, inline links and lists of editable links. | `@isudev/gutenberg/controls/LinkText` | | [`MediaCanvasControl`](/reference/controls/media-canvas-control/) | Renders a media placeholder before selection and an image/video preview with compact replace/remove actions afterward. Each action can be disabled independently. | `@isudev/gutenberg/controls/MediaCanvasControl` | | [`MediaControl`](/reference/controls/media-control/) | The complete single-media editor composed from `MediaCanvasControl`, `MediaToolbarControl` and `MediaSidebarControl`. Every location can be disabled, and each location independently controls its select, replace and remove actions. | `@isudev/gutenberg/controls/MediaControl` | | [`MediaPickerControl`](/reference/controls/media-picker-control/) | Connects any consumer-rendered trigger to WordPress’ native media modal. A render prop exposes `open`, selection state and the current select/replace action, while selections are normalized to a small serializable `MediaValue`. | `@isudev/gutenberg/controls/MediaPickerControl` | | [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) | Adds a media panel to `InspectorControls` with independently configurable actions and one of three preview modes: static media, interactive focal point, or no preview. | `@isudev/gutenberg/controls/MediaSidebarControl` | | [`MediaSourceControl`](/reference/controls/media-source-control/) | Provides the native image-block source workflow as either inline placeholder buttons or a replacement dropdown: media library, upload, direct URL, current post featured image and drag-and-drop. Every source is independently configurable. | `@isudev/gutenberg/controls/MediaSourceControl` | | [`MediaToolbarControl`](/reference/controls/media-toolbar-control/) | Adds state-aware select/replace and remove actions to Gutenberg’s block toolbar without rendering any block content or inspector UI. | `@isudev/gutenberg/controls/MediaToolbarControl` | | [`ResponsiveControl`](/reference/controls/responsive-control/) | Makes any control responsive: renders a label and a breakpoint switcher, then hands the resolved per-breakpoint value to a render prop. | `@isudev/gutenberg/controls/ResponsiveControl` | # BlockLinkControl > Injects an add/edit action and an optional unlink action into Gutenberg's `BlockControls`. The add/edit action opens the same `LinkPickerControl` used by the lower-level and editable-text link APIs. ## Summary [Section titled “Summary”](#summary) Injects an add/edit action and an optional unlink action into Gutenberg’s `BlockControls`. The add/edit action opens the same `LinkPickerControl` used by the lower-level and editable-text link APIs. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when a whole block, card, image or other non-text element stores one link and should expose that link from the selected block’s toolbar. Use `LinkText` when the text itself is editable. Use `LinkPickerControl` when the trigger belongs in custom markup instead of the block toolbar. Render this component from a block’s `edit` function. It is editor UI and must not be used from `save`. ## Import [Section titled “Import”](#import) ```js import { BlockLinkControl } from '@isudev/gutenberg/controls'; ``` Or import the single control: ```js import { BlockLinkControl } from '@isudev/gutenberg/controls/BlockLinkControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ----------------------- | ------------------------------ | ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------- | | `value` | `LinkValue` | `{}` | No | Current destination and link settings. | | `onChange` | `( value: LinkValue ) => void` | — | Yes | Updates the block’s link attribute with a normalized value. | | `onRemove` | `() => void` | `onChange( {} )` | No | Custom unlink behavior. The picker closes after it runs. | | `group` | `BlockControlsGroup` | `'default'` | No | Gutenberg toolbar group: `default`, `block`, `inline`, `other` or `parent`. | | `disabled` | `boolean` | `false` | No | Disables the add/edit and unlink actions. | | `showUnlinkButton` | `boolean` | `false` | No | Shows a separate unlink action while a link exists. | | `addLabel` | `string` | `'Add link'` | No | Accessible label used while no link exists. | | `editLabel` | `string` | `'Edit link'` | No | Accessible label used while a link exists. | | `unlinkLabel` | `string` | `'Unlink'` | No | Accessible label for the unlink action. | | `linkIcon` | `IconType` | WordPress `link` icon | No | Icon used while no link exists. | | `editIcon` | `IconType` | Link-with-pencil icon | No | Icon used while a link exists. | | `unlinkIcon` | `IconType` | WordPress `linkOff` icon | No | Icon for the unlink action. | | `toolbarGroupClassName` | `string` | `undefined` | No | Extra class name on the generated `ToolbarGroup`. | | `pickerProps` | `BlockLinkControlPickerProps` | `undefined` | No | Additional picker and popover options except controlled link props. The native text field is enabled by default. | ## Examples [Section titled “Examples”](#examples) ### Link a whole block [Section titled “Link a whole block”](#link-a-whole-block) ```jsx export function Edit( { attributes, setAttributes } ) { return ( <> setAttributes( { link } ) } />
Linked block content
); } ``` For a static block, apply the shared helper in `save`: ```jsx ``` ### Pages only and custom labels [Section titled “Pages only and custom labels”](#pages-only-and-custom-labels) ```jsx setAttributes( { cardLink } ) } addLabel={ __( 'Link card' ) } editLabel={ __( 'Change card link' ) } group="other" pickerProps={ { noDirectEntry: true, noURLSuggestion: true, suggestionsQuery: { type: 'post', subtype: 'page' }, } } /> ``` The native Text field writes to `value.title` and is included in the object received by `onChange`. Disable it when the linked element has no meaningful label: ```jsx setAttributes( { cardLink } ) } pickerProps={ { hasTextControl: false } } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * The component owns the `BlockControls` and `ToolbarGroup`; consumers only render one control from the block’s `edit` function. * With no URL it displays the add action and WordPress’ link icon. With a URL it displays the edit action and a link-with-pencil icon. Both icons can be replaced independently. Set `showUnlinkButton` to `true` to add a separate unlink action. * The link button anchors the popover. Its pressed/active toolbar state reflects only whether that popover is open; a stored URL changes the icon and label, not the active state. * Opening from the toolbar focuses the picker, matching Gutenberg’s toolbar-triggered link workflow. * WordPress’ native Text field is enabled by default and persists its value as `LinkValue.title`. Unlike `LinkText`, this control does not own or render the block’s visible text, so it does not expose a competing `text` attribute. * Link normalization, custom `rel` preservation and default unlink behavior come from `LinkPickerControl`. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. WordPress provides the block-toolbar, icons, picker and popover styles. Use `toolbarGroupClassName` only for block-specific adjustments. ## Gotchas [Section titled “Gotchas”](#gotchas) * Do not wrap this component in another `BlockControls`; it already creates the fill. * `BlockControls` only displays for the currently selected block, so the component should remain mounted as part of the block’s normal edit tree. * Unlinking remains available from WordPress’ link picker when the separate toolbar action is hidden. * A custom `onRemove` must clear the stored link itself. ## Related [Section titled “Related”](#related) * [`LinkPickerControl`](/reference/controls/link-picker-control/) — custom trigger and popover composition. * [`LinkText`](/reference/controls/link-text/) — editable linked text with native focus behavior. # LinkPickerControl > Adds WordPress' native link picker to any consumer-rendered element. It owns popover state and link normalization, while a render prop keeps the trigger and the block's markup under the consumer's control. ## Summary [Section titled “Summary”](#summary) Adds WordPress’ native link picker to any consumer-rendered element. It owns popover state and link normalization, while a render prop keeps the trigger and the block’s markup under the consumer’s control. The same folder exports `normalizeLinkValue` and `getLinkAttributes`, so editor state and saved anchor attributes use one link model. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when a card, image, button, toolbar action or another custom element needs a link but must keep its own markup. Use `LinkText` when the editable element is simply inline link text. Do not import WordPress’ `LinkControl` directly unless you also want to own its popover, anchor lifecycle, unlink behavior and `rel` normalization. ## Import [Section titled “Import”](#import) ```js import { getLinkAttributes, LinkPickerControl, } from '@isudev/gutenberg/controls'; ``` Or import only this entry: ```js import { getLinkAttributes, LinkPickerControl, } from '@isudev/gutenberg/controls/LinkPickerControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------------- | --------------------------------------------- | ------------------------ | -------- | ------------------------------------------------------------------- | | `value` | `LinkValue` | `{}` | No | Current serializable link value. | | `onChange` | `( value: LinkValue ) => void` | — | Yes | Receives normalized URL, title, entity data, settings and `rel`. | | `onRemove` | `() => void` | `onChange( {} )` | No | Custom unlink behavior. The picker closes afterward. | | `children` | `( args: LinkPickerRenderArgs ) => ReactNode` | — | Yes | Renders any trigger UI and receives its anchor ref and actions. | | `isOpen` | `boolean` | `undefined` | No | Controls popover visibility externally. | | `defaultOpen` | `boolean` | `false` | No | Initial visibility when uncontrolled. | | `onOpenChange` | `( isOpen: boolean ) => void` | `undefined` | No | Observes open/close requests in either mode. | | `settings` | `LinkSetting[]` | New-tab and nofollow | No | Settings shown in WordPress’ link settings drawer. | | `suggestionsQuery` | `LinkSuggestionsQuery` | `undefined` | No | Restricts suggestions, e.g. to one post type or taxonomy. | | `showSuggestions` | `boolean` | `true` | No | Enables search suggestions. | | `showInitialSuggestions` | `boolean` | `true` | No | Shows suggestions before typing, matching WordPress’ native picker. | | `forceIsEditingLink` | `boolean` | `true` for an empty link | No | Forces the URL search/editor instead of the saved-link preview. | | `noDirectEntry` | `boolean` | `false` | No | Prevents arbitrary URL entry. | | `noURLSuggestion` | `boolean` | `false` | No | Hides the fallback that treats typed text as a URL. | | `hasTextControl` | `boolean` | `false` | No | Shows WordPress’ title input in the picker. | | `handleEntities` | `boolean` | `false` | No | Locks an entity link until it is explicitly unlinked. | | `hasRichPreviews` | `boolean` | `true` | No | Enables WordPress’ rich preview for selected entities. | | `searchInputPlaceholder` | `string` | WordPress default | No | Picker search-input placeholder. | | `popoverPlacement` | `PopoverPlacement` | `'bottom-start'` | No | Placement relative to the anchored element. | | `popoverOffset` | `number` | `8` | No | Gap from the anchor in pixels. | | `popoverNoArrow` | `boolean` | `false` | No | Hides the popover arrow. | | `popoverClassName` | `string` | `undefined` | No | Extra class on the popover. | | `popoverFocusOnMount` | `'firstElement' \| boolean` | `'firstElement'` | No | Controls focus when the popover opens. | | `popoverAnimate` | `boolean` | `false` | No | Animates the popover when enabled; native inline links disable it. | | `popoverShift` | `boolean` | `true` | No | Shifts the popover to keep it inside the viewport. | | `popoverConstrainTabbing` | `boolean` | `true` | No | Keeps tab navigation inside the open popover. | | `popoverHeader` | `ReactNode` | `undefined` | No | Content above WordPress’ picker. | | `popoverFooter` | `ReactNode` | `undefined` | No | Content below WordPress’ picker. | The render prop receives `anchorRef`, `isOpen`, `hasLink`, `open`, `close`, `toggle` and `remove`. Attach `anchorRef` to the actual element the popover should follow; this uses local state, so it also works inside the iframe editor. ## Examples [Section titled “Examples”](#examples) ### Link an entire card [Section titled “Link an entire card”](#link-an-entire-card) ```jsx const { cardLink } = attributes; setAttributes( { cardLink: next } ) } > { ( { anchorRef, open, hasLink, remove } ) => (
{ hasLink && }
) }
``` For a static block, use the same model in `save`: ```jsx ``` ### Limit suggestions to pages and control the popover [Section titled “Limit suggestions to pages and control the popover”](#limit-suggestions-to-pages-and-control-the-popover) ```jsx const [ isOpen, setIsOpen ] = useState( false ); setAttributes( { link: linkValue } ) } isOpen={ isOpen } onOpenChange={ setIsOpen } noDirectEntry noURLSuggestion suggestionsQuery={ { type: 'post', subtype: 'page' } } popoverPlacement="bottom" > { ( { anchorRef, toggle } ) => ( ) } ``` ### Dynamic PHP rendering [Section titled “Dynamic PHP rendering”](#dynamic-php-rendering) `getLinkAttributes` is for JSX serialization. A dynamic block must escape each value on the server: ```php ``` Build `target` and `rel` with `wp_targeted_link_rel()` or equivalent server-side logic; do not trust attributes merely because the editor normalized them. ## Behavior [Section titled “Behavior”](#behavior) * The component is controlled for link data and optionally controlled for popover state. * WordPress 7’s stable `LinkControl` provides URL search, direct entry and entity selection. * An empty value is passed to WordPress as `null`, with editing forced and initial suggestions enabled. This is the same new-link mode used by WordPress’ `LinkPicker`; passing a truthy, normalized empty object changes `LinkControl`’s internal state machine and is deliberately avoided. * Existing values retain WordPress’ preview → edit transition. Rich previews are enabled by default. * `normalizeLinkValue` trims the URL, deduplicates `rel`, keeps consumer tokens such as `ugc`/`sponsored`, synchronizes `nofollow`, and adds `noopener noreferrer` for `_blank`. * Default unlink calls `onChange( {} )`; supplying `onRemove` transfers storage cleanup to the consumer. Both paths close the popover. * `getLinkAttributes` omits blank links and obvious executable protocols (`javascript:`, `data:`, `vbscript:`), but server-rendered output still requires WordPress escaping. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. WordPress owns the picker and popover styles; the trigger markup is entirely yours. ## Gotchas [Section titled “Gotchas”](#gotchas) * `children` is a function, not a React element. * Attach `anchorRef` to a mounted HTML element. Opening before an anchor exists intentionally renders no popover. * In controlled `isOpen` mode, `onOpenChange` is a request; the parent must update `isOpen`. * Use `popoverFocusOnMount="firstElement"` for a button/toolbar action. Use `false` when the picker was opened by clicking editable text, so the caret is not stolen. * `LinkValue.title` is the selected entity title. It is not the HTML `title` attribute unless a consumer deliberately maps it there. * A static block’s attributes remain author-controlled data. The URL guard is defense in depth, not a substitute for server-side escaping in dynamic blocks. ## Related [Section titled “Related”](#related) * [`LinkText`](/reference/controls/link-text/) — ready-made RichText link UI. * [`BlockLinkControl`](/reference/controls/block-link-control/) — ready-made block-toolbar actions. * [WordPress block attributes and serialization](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/). # LinkText > Provides editable `RichText` rendered as an anchor and a native-style link action in the inline block toolbar. It is the ready-made path for CTA labels, inline links and lists of editable links. ## Summary [Section titled “Summary”](#summary) Provides editable `RichText` rendered as an anchor and a native-style link action in the inline block toolbar. It is the ready-made path for CTA labels, inline links and lists of editable links. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when both the link text and destination belong to the block’s attributes. Use `LinkPickerControl` when the linked UI is a card, image, composite component or anything other than editable text. Use `BlockLinkControl` when an entire block only needs toolbar actions and no editable link text. This is editor UI, not a frontend component. Serialize with `RichText.Content` and `getLinkAttributes`, or escape the values in a dynamic PHP render. ## Import [Section titled “Import”](#import) ```js import { LinkText } from '@isudev/gutenberg/controls'; ``` Or import the single control: ```js import { LinkText } from '@isudev/gutenberg/controls/LinkText'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ----------------------- | ----------------------------- | ------------------------------- | -------- | ----------------------------------------------------------------------- | | `text` | `string` | `''` | No | Current RichText content. | | `onTextChange` | `( text: string ) => void` | — | Yes | Updates the text attribute. | | `link` | `LinkValue` | `{}` | No | Current destination and link settings. | | `onLinkChange` | `( link: LinkValue ) => void` | — | Yes | Updates the normalized link attribute. | | `onLinkRemove` | `() => void` | `onLinkChange( {} )` | No | Custom unlink behavior. | | `placeholder` | `string` | `'Link text…'` | No | Placeholder for empty text. | | `className` | `string` | `undefined` | No | Extra class on the editor anchor. | | `ariaLabel` | `string` | Text or `'Link text'` | No | Accessible name for the editable anchor. | | `allowedFormats` | `string[]` | `[]` | No | RichText formats permitted inside the link. | | `disableLineBreaks` | `boolean` | `true` | No | Prevents multiline link labels. | | `showIncompleteWarning` | `boolean` | `true` | No | Shows an editor-only warning if text or a safe URL is missing. | | `incompleteWarningText` | `string` | `'Link text or URL is missing'` | No | Warning tooltip and accessible label. | | `warningSuffix` | `ReactNode` | `undefined` | No | Extra content beside the warning icon. | | `pickerProps` | `LinkTextPickerProps` | `undefined` | No | Additional picker and popover options except its controlled link props. | | `richTextProps` | `Record` | `undefined` | No | Additional RichText props; LinkText’s controlled props take precedence. | | `showToolbarButton` | `boolean` | `true` | No | Adds the native-style link action to `BlockControls`. | | `toolbarLabel` | `string` | `'Link'` | No | Accessible title for the toolbar action. | | `toolbarIcon` | `IconType` | WordPress `link` icon | No | Toolbar icon used while no link exists. | | `toolbarEditIcon` | `IconType` | Link-with-pencil icon | No | Toolbar icon used while a link exists. | ## Examples [Section titled “Examples”](#examples) ### Minimal editable link [Section titled “Minimal editable link”](#minimal-editable-link) ```jsx const { linkText, link } = attributes; setAttributes( { linkText: nextText } ) } onLinkChange={ ( nextLink ) => setAttributes( { link: nextLink } ) } /> ``` Declare the attributes in `block.json`: ```json { "attributes": { "linkText": { "type": "string", "default": "" }, "link": { "type": "object", "default": {} } } } ``` Serialize a static block: ```jsx ``` ### Pages only and custom formatting [Section titled “Pages only and custom formatting”](#pages-only-and-custom-formatting) ```jsx setAttributes( { ctaText } ) } onLinkChange={ ( ctaLink ) => setAttributes( { ctaLink } ) } allowedFormats={ [ 'core/bold' ] } pickerProps={ { noDirectEntry: true, noURLSuggestion: true, suggestionsQuery: { type: 'post', subtype: 'page' }, popoverPlacement: 'bottom', } } richTextProps={ { identifier: 'ctaText' } } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Clicking unlinked text only places the caret and never opens the picker, so typing remains uninterrupted. Add the destination through the link action in the block toolbar. * Opening from the toolbar autofocuses WordPress’ search field, forces its new-link mode and shows initial suggestions. Gutenberg only displays the `BlockControls` fill for the active block. * The toolbar uses the regular link icon without a destination and the link-with-pencil icon with a destination. Its active state reflects only an open picker, never the stored URL. * Clicking text that already has an `href` opens WordPress’ link preview but passes `focusOnMount={ false }`, exactly like the native RichText link flow, so the popup does not steal the caret. * The picker receives the current RichText value as its native Text field. Changes made in that field update `text`; selecting an entity preserves existing text and fills empty text with the entity title. * Link data is normalized by `LinkPickerControl`; the editor anchor previews its `href`, `_blank` target and managed `rel` values. * The warning is editor-only. It appears for empty text, a missing URL or a URL rejected by `getLinkAttributes`. * Controlled RichText props (`tagName`, `ref`, value callbacks, link attributes and click behavior) override conflicting entries in `richTextProps`. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Style the editable anchor through `className` or block editor styles. The warning uses the WordPress caution icon and a small inline layout style only. ## Gotchas [Section titled “Gotchas”](#gotchas) * `text` may contain RichText markup. Use `RichText.Content` for static JSX output and `wp_kses_post()` for an equivalent dynamic PHP render. * `LinkValue.title` is used as plain text when auto-filling an empty label. * Overriding `pickerProps.popoverFocusOnMount` also overrides the native split between toolbar autofocus and existing-link click behavior. Do so only when supplying an equivalent focus strategy. * `onLinkRemove` must clear the consumer’s stored link. If it does not, the old destination will remain controlled and reappear. * Do not put another interactive element inside the editable anchor through `richTextProps`. ## Related [Section titled “Related”](#related) * [`LinkPickerControl`](/reference/controls/link-picker-control/) — the lower-level arbitrary-element picker. * [`BlockLinkControl`](/reference/controls/block-link-control/) — ready-made link/unlink block toolbar. # MediaCanvasControl > Renders a media placeholder before selection and an image/video preview with compact replace/remove actions afterward. Each action can be disabled independently. ## Summary [Section titled “Summary”](#summary) Renders a media placeholder before selection and an image/video preview with compact replace/remove actions afterward. Each action can be disabled independently. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it for direct editing on the block canvas. Use `MediaToolbarControl` for toolbar-only actions, `MediaSidebarControl` for inspector UI, or `MediaControl` to combine them. ## Import [Section titled “Import”](#import) ```js import { MediaCanvasControl } from '@isudev/gutenberg/controls'; import { MediaCanvasControl } from '@isudev/gutenberg/controls/MediaCanvasControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------------- | ---------------------------------- | ------------------ | -------- | -------------------------------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized media selections. | | `onRemove` | `() => void` | `onChange( {} )` | No | Custom clearing behavior. | | `actions` | `MediaActionsConfig` | all enabled | No | Independently controls select, replace and remove. | | `sources` | `MediaSourcesConfig` | all enabled | No | Independently controls library, upload, URL, featured image and drop zone. | | `placeholder` | `boolean` | `true` | No | Renders the native-style empty canvas surface. | | `selectLabel` | `string` | `'Select media'` | No | Initial selection label. | | `replaceLabel` | `string` | `'Replace media'` | No | Existing-media action label. | | `removeLabel` | `string` | `'Remove media'` | No | Clear action label. | | `placeholderLabel` | `string` | `'Image'` | No | Placeholder heading. | | `placeholderInstructions` | `string` | selection guidance | No | Placeholder help text. | | `pickerProps` | `MediaCanvasPickerProps` | `undefined` | No | Native picker options except controlled props. | | `previewProps` | `Omit` | `undefined` | No | Media preview configuration. | | `className` | `string` | `undefined` | No | Canvas/placeholder class name. | | `style` | `CSSProperties` | `undefined` | No | Canvas wrapper style after selection. | ## Nested options [Section titled “Nested options”](#nested-options) `actions` accepts `false` to hide every action, or an object with these independently optional switches (each defaults to `true`): | Field | Visible state | Description | | --------- | -------------- | -------------------------------- | | `select` | No media | Shows the initial picker action. | | `replace` | Media selected | Shows the edit/replace action. | | `remove` | Media selected | Shows the remove action. | `sources` is `false` or an object with `library`, `upload`, `url`, `featured` and `dropZone` booleans. Every source defaults to enabled. `pickerProps` accepts `allowedTypes`, `accept`, `imageSize`, `disabled`, `featuredMedia`, `onFilesUpload`, `onError`, `title`, `modalClass`, `onClose`, `fallback` and `labels` from `MediaSourceControl`. `previewProps` accepts every `MediaPreview` prop except its controlled `value`. ## Examples [Section titled “Examples”](#examples) ### Complete canvas editor [Section titled “Complete canvas editor”](#complete-canvas-editor) ```jsx setAttributes( { media } ) } /> ``` ### No empty canvas surface [Section titled “No empty canvas surface”](#no-empty-canvas-surface) ```jsx setAttributes( { media } ) } placeholder={ false } sources={ { upload: false, featured: false } } /> ``` With no selected media this renders nothing; toolbar or sidebar controls can still provide selection. Once media exists, the preview and configured actions render normally. ### Preview without overlay removal [Section titled “Preview without overlay removal”](#preview-without-overlay-removal) ```jsx setAttributes( { media } ) } actions={ { remove: false } } pickerProps={ { allowedTypes: ['image'], imageSize: 'large' } } previewProps={ { aspectRatio: '4 / 3' } } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Without media, the default placeholder exposes upload, media library, URL, featured-image and drag/drop sources. `placeholder={false}` suppresses the complete empty surface. * With media, replace opens the source dropdown. Reset lives in that menu when replace and remove are enabled; remove stays standalone when replace is disabled. * `actions={ false }` leaves the placeholder/preview intact and removes every action. * The selected preview comes from the pure `MediaPreview` component. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. The selected-media wrapper and compact overlay use minimal inline layout styles; `className`, `style` and `previewProps` provide block-specific control. ## Gotchas [Section titled “Gotchas”](#gotchas) `style` applies after selection. `dropZone` applies only to the empty placeholder. ## Related [Section titled “Related”](#related) * [`MediaPreview`](/reference/components/media-preview/) * [`MediaPickerControl`](/reference/controls/media-picker-control/) * [`MediaSourceControl`](/reference/controls/media-source-control/) * [`MediaControl`](/reference/controls/media-control/) # MediaControl > The complete single-media editor composed from `MediaCanvasControl`, `MediaToolbarControl` and `MediaSidebarControl`. Every location can be disabled, and each location independently controls its select, replace and remove actions. ## Summary [Section titled “Summary”](#summary) The complete single-media editor composed from `MediaCanvasControl`, `MediaToolbarControl` and `MediaSidebarControl`. Every location can be disabled, and each location independently controls its select, replace and remove actions. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it for the common block workflow where one media value needs canvas, toolbar and/or sidebar editing. Import an individual submodule when only one location is required so the consumer bundle does not include the other surfaces. ## Import [Section titled “Import”](#import) ```js import { MediaControl } from '@isudev/gutenberg/controls'; import { MediaControl } from '@isudev/gutenberg/controls/MediaControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------------- | ------------------------------------------------- | ---------------- | -------- | --------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized selections. | | `onRemove` | `() => void` | `onChange( {} )` | No | Shared custom remove behavior. | | `focalPoint` | `MediaFocalPoint` | `undefined` | No | Focal point passed to the sidebar. | | `onFocalPointChange` | `( value: MediaFocalPoint \| undefined ) => void` | `undefined` | No | Enables focal-point editing. | | `allowedTypes` | `string[]` | `['image']` | No | Default allowed types for every location. | | `imageSize` | `string` | `undefined` | No | Default image rendition for every location. | | `disabled` | `boolean` | `false` | No | Disables actions in every location. | | `sources` | `MediaSourcesConfig` | all enabled | No | Default media-source visibility for every location. | | `resetFocalPointOnChange` | `boolean` | `false` | No | Resets focal point after replace/remove. | | `canvas` | `false \| MediaControlCanvasOptions` | `{}` | No | Configures or disables inline editing. | | `toolbar` | `false \| MediaControlToolbarOptions` | `{}` | No | Configures or disables toolbar editing. | | `sidebar` | `false \| MediaControlSidebarOptions` | `{}` | No | Configures or disables inspector editing. | ## Location configuration [Section titled “Location configuration”](#location-configuration) Passing `false` removes a location and its WordPress fill completely. An object accepts the following options; `value`, change handlers and focal-point state remain owned by `MediaControl`: | Location | Supported option fields | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `canvas` | `actions`, `sources`, `placeholder`, `selectLabel`, `replaceLabel`, `removeLabel`, `placeholderLabel`, `placeholderInstructions`, `pickerProps`, `previewProps`, `className`, `style` | | `toolbar` | `actions`, `sources`, `group`, `selectLabel`, `replaceLabel`, `removeLabel`, `toolbarGroupClassName`, `pickerProps` | | `sidebar` | `actions`, `sources`, `preview`, `title`, `initialOpen`, `selectLabel`, `replaceLabel`, `removeLabel`, `pickerProps`, `previewProps`, `focalPointProps`, `className` | For every location, `actions` is either `false` or an object with optional `select`, `replace` and `remove` booleans; every action defaults to visible in the state where it is relevant. Sidebar `preview` accepts `'media'`, `'focal-point'` or `false`. At the composite or location level, `sources` is either `false` or an object with optional `library`, `upload`, `url`, `featured` and `dropZone` booleans. Every source defaults to enabled. A location’s `sources` overrides the composite default. `dropZone` applies only to the empty canvas placeholder. Canvas `placeholder={false}` removes that empty surface while leaving toolbar/sidebar selection available. Each location’s `pickerProps` can override the common `allowedTypes`, `imageSize` and `disabled` values and additionally accepts `title`, `modalClass`, `onClose` and `fallback`. The full meaning and defaults of location-specific options are documented in the linked submodule READMEs below. ## Examples [Section titled “Examples”](#examples) ### Complete image control [Section titled “Complete image control”](#complete-image-control) ```jsx setAttributes( { media } ) } focalPoint={ attributes.focalPoint } onFocalPointChange={ ( focalPoint ) => setAttributes( { focalPoint } ) } resetFocalPointOnChange sidebar={ { preview: 'focal-point' } } /> ``` ### Canvas plus limited toolbar, no sidebar [Section titled “Canvas plus limited toolbar, no sidebar”](#canvas-plus-limited-toolbar-no-sidebar) ```jsx setAttributes( { media } ) } allowedTypes={ ['image', 'video'] } sources={ { featured: false } } canvas={ { actions: { remove: false } } } toolbar={ { actions: { select: false, remove: true } } } sidebar={ false } /> ``` Each location can be removed completely: ```jsx setAttributes( { media } ) } canvas={ false } toolbar={ false } sidebar={ { preview: false, actions: { remove: false } } } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Canvas, toolbar and sidebar are enabled by default. * Common picker settings are inherited by every location; location `pickerProps` override common values. * Common media-source switches are inherited by every location; location `sources` override the composite value. * All locations share one change/remove pipeline. * Optional focal reset runs only when media identity changes or media is removed. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Each submodule uses WordPress UI plus minimal inline layout styles. ## Gotchas [Section titled “Gotchas”](#gotchas) * This component intentionally handles one media value, not galleries, captions or embeds. * Direct URLs have no attachment ID. Featured-image values stay synchronized only while their stored `source` remains `'featured'`. * If only one surface is required, import that surface directly for the narrowest bundle. * Store the normalized media object, not only its ID, so pure previews work after reload. ## Related [Section titled “Related”](#related) * [`MediaCanvasControl`](/reference/controls/media-canvas-control/) * [`MediaToolbarControl`](/reference/controls/media-toolbar-control/) * [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) * [`MediaPickerControl`](/reference/controls/media-picker-control/) * [`MediaSourceControl`](/reference/controls/media-source-control/) # MediaPickerControl > Connects any consumer-rendered trigger to WordPress' native media modal. A render prop exposes `open`, selection state and the current select/replace action, while selections are normalized to a small serializable `MediaValue`. ## Summary [Section titled “Summary”](#summary) Connects any consumer-rendered trigger to WordPress’ native media modal. A render prop exposes `open`, selection state and the current select/replace action, while selections are normalized to a small serializable `MediaValue`. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when the trigger belongs to custom markup. Use `MediaCanvasControl`, `MediaToolbarControl` or `MediaSidebarControl` for ready-made locations, and `MediaControl` to compose all locations. ## Import [Section titled “Import”](#import) ```js import { MediaPickerControl } from '@isudev/gutenberg/controls'; import { MediaPickerControl } from '@isudev/gutenberg/controls/MediaPickerControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | -------------- | --------------------------------------------------------- | ----------------- | -------- | -------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media value. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized and native media values. | | `children` | `( args: MediaPickerRenderArgs ) => ReactElement \| null` | — | Yes | Renders the modal trigger. | | `allowedTypes` | `string[]` | `['image']` | No | Allowed WordPress media or MIME types. | | `imageSize` | `string` | `undefined` | No | Preferred image rendition with full-size fallback. | | `disabled` | `boolean` | `false` | No | Makes the exposed `open` function a no-op. | | `title` | `string` | WordPress default | No | Native media modal title. | | `modalClass` | `string` | `undefined` | No | Class name added to the native modal. | | `onClose` | `() => void` | `undefined` | No | Called whenever the media modal closes. | | `fallback` | `ReactNode` | `null` | No | Rendered when the user cannot upload media. | ## Render arguments [Section titled “Render arguments”](#render-arguments) | Field | Type | Description | | ---------- | ----------------------- | ---------------------------------------------------------- | | `open` | `() => void` | Opens the native media modal; it is a no-op when disabled. | | `hasMedia` | `boolean` | True when `value` has an attachment ID or URL. | | `disabled` | `boolean` | The resolved disabled state for a custom trigger. | | `action` | `'select' \| 'replace'` | State-dependent action derived from `hasMedia`. | ## Normalized media value [Section titled “Normalized media value”](#normalized-media-value) `onChange` receives this serializable `MediaValue` as its first argument and the untouched WordPress selection as its optional second argument: | Field | Type | Source | | -------- | -------------- | ---------------------------------------------------------------- | | `source` | `'attachment'` | Native media-library selection. | | `id` | `number` | Attachment ID. | | `url` | `string` | Requested `imageSize`, then `source_url`, then the original URL. | | `type` | `string` | Broad media type, inferred from the MIME type when necessary. | | `mime` | `string` | Native `mime_type` or `mime`. | | `alt` | `string` | Native `alt_text` or `alt`. | | `width` | `number` | Requested rendition width, then original width. | | `height` | `number` | Requested rendition height, then original height. | ## Exported helpers [Section titled “Exported helpers”](#exported-helpers) | Helper | Signature | Description | | --------------------- | --------------------------------------------------------------------- | ----------------------------------------- | | `normalizeMediaValue` | `( media: unknown, imageSize?: string ) => MediaValue` | Normalizes a native WordPress selection. | | `hasMediaValue` | `( value?: MediaValue ) => boolean` | Checks for an attachment ID or URL. | | `resolveMediaActions` | `( actions?: MediaActionsConfig ) => Required` | Resolves default-visible action switches. | ## Examples [Section titled “Examples”](#examples) ### Custom button [Section titled “Custom button”](#custom-button) ```jsx setAttributes( { media } ) } > { ( { open, action } ) => ( ) } ``` ### Video and selected rendition [Section titled “Video and selected rendition”](#video-and-selected-rendition) ```jsx { setAttributes( { media } ); console.log( nativeMedia ); } } allowedTypes={ ['image', 'video'] } imageSize="large" > { ( { open, hasMedia } ) => ( ) } ``` ## Behavior [Section titled “Behavior”](#behavior) * Wraps `MediaUpload` in `MediaUploadCheck`. * `hasMedia` is true when the controlled value contains an ID or URL. * `imageSize` reads the requested rendition from WordPress’ selection and falls back to `source_url`/`url`. * Normalized native selections include `source: 'attachment'`; `MediaSourceControl` adds URL and featured-image source values. * The three normalization/state helpers above are exported from both the direct entry point and the controls barrel. ## Styling [Section titled “Styling”](#styling) Ships no markup around the render prop and no stylesheet. ## Gotchas [Section titled “Gotchas”](#gotchas) This is a single-media picker. Gallery/multiple selection is intentionally excluded from the base API because its value and editing semantics are different and deserve a separate module. ## Related [Section titled “Related”](#related) * [`MediaCanvasControl`](/reference/controls/media-canvas-control/) * [`MediaSourceControl`](/reference/controls/media-source-control/) * [`MediaToolbarControl`](/reference/controls/media-toolbar-control/) * [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) * [`MediaControl`](/reference/controls/media-control/) # MediaSidebarControl > Adds a media panel to `InspectorControls` with independently configurable actions and one of three preview modes: static media, interactive focal point, or no preview. ## Summary [Section titled “Summary”](#summary) Adds a media panel to `InspectorControls` with independently configurable actions and one of three preview modes: static media, interactive focal point, or no preview. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it for inspector-only media editing. Use `MediaFocalPointControl` directly when no panel should be created, or `MediaControl` to combine the sidebar with canvas and toolbar locations. ## Import [Section titled “Import”](#import) ```js import { MediaSidebarControl } from '@isudev/gutenberg/controls'; import { MediaSidebarControl } from '@isudev/gutenberg/controls/MediaSidebarControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | -------------------- | ----------------------------------------------------- | ------------------ | -------- | -------------------------------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized selections. | | `onRemove` | `() => void` | `onChange( {} )` | No | Custom clearing behavior. | | `actions` | `MediaActionsConfig` | all enabled | No | Independently controls select, replace and remove. | | `sources` | `MediaSourcesConfig` | all enabled | No | Independently controls library, upload, URL, featured image and drop zone. | | `preview` | `false \| 'media' \| 'focal-point'` | `'media'` | No | Selects or disables sidebar preview UI. | | `focalPoint` | `MediaFocalPoint` | `undefined` | No | Controlled focal point. | | `onFocalPointChange` | `( value: MediaFocalPoint \| undefined ) => void` | `undefined` | No | Enables focal-point editing and receives changes. | | `title` | `string` | `'Media settings'` | No | Inspector panel title. | | `initialOpen` | `boolean` | `true` | No | Initial panel expansion state. | | `selectLabel` | `string` | `'Select media'` | No | Initial action label. | | `replaceLabel` | `string` | `'Replace media'` | No | Existing-media action label. | | `removeLabel` | `string` | `'Remove media'` | No | Clear action label. | | `pickerProps` | `MediaSidebarPickerProps` | `undefined` | No | Native picker options except controlled props. | | `previewProps` | `Omit` | `undefined` | No | Static preview options. | | `focalPointProps` | `Omit` | `undefined` | No | Focal-point display options. | | `className` | `string` | `undefined` | No | Additional `PanelBody` class name. | ## Nested options [Section titled “Nested options”](#nested-options) `actions` accepts `false` to hide every sidebar action, or an object whose fields each default to `true`: | Field | Visible state | Description | | --------- | -------------- | -------------------------------- | | `select` | No media | Shows the initial picker button. | | `replace` | Media selected | Shows the edit/replace button. | | `remove` | Media selected | Shows the remove button. | The `preview` modes are: | Value | Result | | --------------- | --------------------------------------------------------------------------- | | `'media'` | Static `MediaPreview`; configured through `previewProps`. | | `'focal-point'` | Interactive `MediaFocalPointControl`; configured through `focalPointProps`. | | `false` | No preview; action buttons remain independent. | `sources` is `false` or an object with `library`, `upload`, `url`, `featured` and `dropZone` booleans. `dropZone` has no effect in the sidebar dropdown. `pickerProps` accepts `allowedTypes`, `accept`, `imageSize`, `disabled`, `featuredMedia`, `onFilesUpload`, `onError`, `title`, `modalClass`, `onClose`, `fallback` and `labels`. `previewProps` accepts every `MediaPreview` prop except `value`. `focalPointProps` accepts `label`, `help`, `hideLabelFromVision`, `autoPlay`, `showReset`, `resetLabel` and `emptyFallback`. ## Examples [Section titled “Examples”](#examples) ### Static preview and actions [Section titled “Static preview and actions”](#static-preview-and-actions) ```jsx setAttributes( { media } ) } preview="media" /> ``` ### Focal point without sidebar buttons [Section titled “Focal point without sidebar buttons”](#focal-point-without-sidebar-buttons) ```jsx setAttributes( { media } ) } actions={ false } preview="focal-point" focalPoint={ attributes.focalPoint } onFocalPointChange={ ( focalPoint ) => setAttributes( { focalPoint } ) } /> ``` Disable only the preview while retaining buttons with `preview={ false }`. ## Behavior [Section titled “Behavior”](#behavior) * `preview="media"` renders `MediaPreview` only when a URL exists. * `preview="focal-point"` requires `onFocalPointChange`; development builds warn and fall back to the static preview when it is missing. * Preview visibility and action visibility are independent. * Select/replace opens the same source dropdown as the toolbar. Reset is inside that menu when replace and remove are enabled. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. WordPress supplies inspector styles; preview spacing and button layout use minimal inline styles. ## Gotchas [Section titled “Gotchas”](#gotchas) The component owns `InspectorControls` and `PanelBody`; render it directly from `edit`. ## Related [Section titled “Related”](#related) * [`MediaPreview`](/reference/components/media-preview/) * [`MediaFocalPointControl`](/reference/components/media-focal-point-control/) * [`MediaToolbarControl`](/reference/controls/media-toolbar-control/) * [`MediaSourceControl`](/reference/controls/media-source-control/) * [`MediaControl`](/reference/controls/media-control/) # MediaSourceControl > Provides the native image-block source workflow as either inline placeholder buttons or a replacement dropdown: media library, upload, direct URL, current post featured image and drag-and-drop. Every source is independently configurable. ## Summary [Section titled “Summary”](#summary) Provides the native image-block source workflow as either inline placeholder buttons or a replacement dropdown: media library, upload, direct URL, current post featured image and drag-and-drop. Every source is independently configurable. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it to add the same source workflow to custom markup. Use `MediaCanvasControl`, `MediaToolbarControl`, `MediaSidebarControl` or `MediaControl` when the library should also create the editor surface. Use `MediaPickerControl` when a single custom trigger should open only the media library. This is a single-media control, not a gallery or embed renderer. ## Import [Section titled “Import”](#import) ```js import { MediaSourceControl } from '@isudev/gutenberg/controls'; import { MediaSourceControl } from '@isudev/gutenberg/controls/MediaSourceControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | --------------- | --------------------------------------------------------- | ------------------- | -------- | -------------------------------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media value. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized attachment, URL and featured-image selections. | | `onRemove` | `() => void` | `undefined` | No | Enables the reset item while a value exists. | | `sources` | `MediaSourcesConfig` | all enabled | No | Independently controls library, upload, URL, featured image and drop zone. | | `variant` | `'buttons' \| 'dropdown'` | `'dropdown'` | No | Selects inline placeholder buttons or the replacement menu. | | `allowedTypes` | `string[]` | `['image']` | No | Allowed WordPress media types or MIME types. | | `accept` | `string` | inferred | No | Native file-input accept value; overrides inference from `allowedTypes`. | | `imageSize` | `string` | `undefined` | No | Preferred WordPress image rendition. | | `disabled` | `boolean` | `false` | No | Disables every source interaction. | | `featuredMedia` | `MediaValue \| null` | auto-resolved | No | Overrides the current post featured image; `null` marks it unavailable. | | `onFilesUpload` | `( files: File[] \| FileList ) => void` | `undefined` | No | Runs before direct files enter WordPress’ uploader. | | `onError` | `( message: string ) => void` | `undefined` | No | Receives upload errors. | | `title` | `string` | WordPress default | No | Native media modal title. | | `modalClass` | `string` | `undefined` | No | Class added to the native media modal. | | `onClose` | `() => void` | `undefined` | No | Runs whenever the media modal closes. | | `fallback` | `ReactNode` | `null` | No | Replaces permission-gated library/upload actions. | | `labels` | `Partial` | translated defaults | No | Overrides source, toggle, reset and URL-form labels. | | `children` | `( args: MediaSourceToggleArgs ) => ReactElement \| null` | default button | No | Custom dropdown toggle; ignored by `variant="buttons"`. | ## Source configuration [Section titled “Source configuration”](#source-configuration) `sources={false}` hides every source. An object overrides the following default-enabled fields independently: | Field | Buttons variant | Dropdown variant | | ---------- | ---------------------------------------- | --------------------------------------- | | `library` | Shows `Media Library`. | Shows `Open Media Library`. | | `upload` | Shows `Upload`. | Shows the direct-upload menu item. | | `url` | Shows `Insert from URL` and its popover. | Shows the current-media URL form. | | `featured` | Shows `Use featured image`. | Shows the featured-image menu item. | | `dropZone` | Accepts drag-and-drop uploads. | Ignored; dropdowns have no drop target. | `onRemove` is deliberately separate: reset is an action, not a media source. It remains available even with `sources={false}`. `labels` accepts `select`, `replace`, `library`, `upload`, `url`, `featured`, `remove`, `currentUrl` and `applyUrl`. ## Examples [Section titled “Examples”](#examples) ### Native image-placeholder sources [Section titled “Native image-placeholder sources”](#native-image-placeholder-sources) Render this inside a WordPress `Placeholder`: ```jsx setAttributes( { media } ) } /> ``` ### Replacement dropdown with selected sources [Section titled “Replacement dropdown with selected sources”](#replacement-dropdown-with-selected-sources) ```jsx setAttributes( { media } ) } onRemove={ () => setAttributes( { media: {} } ) } sources={ { upload: false, dropZone: false } } > { ( { toggle, isOpen, disabled, label } ) => ( ) } ``` ### Injected featured image outside the post editor [Section titled “Injected featured image outside the post editor”](#injected-featured-image-outside-the-post-editor) ```jsx setAttributes( { media } ) } featuredMedia={ { id: 12, url: '/featured.jpg', type: 'image' } } sources={ { library: false, upload: false, url: false } } /> ``` ## Behavior [Section titled “Behavior”](#behavior) * Attachment choices are normalized with `source: 'attachment'`; direct URLs use `source: 'url'`; featured images use `source: 'featured'`. * When a selected value has `source: 'featured'`, it follows later changes to the current post’s featured image. Removing the post featured image clears the media fields but keeps featured mode, so assigning a new one restores the value. Choosing another source stops that synchronization. * `featuredMedia={undefined}` reads `featured_media` from the current post and its attachment from `core-data`. Passing `null` prevents automatic resolution. * The media picker and file input live outside the dropdown content. This avoids Gutenberg’s blank media-modal failure when a dropdown is rendered inside an iframe block. * URL values are trimmed and emitted as data; the component never injects URL content or raw embed HTML. * A direct URL keeps the current broad media type or uses the first `allowedTypes` entry, so place the intended URL type first when allowing both images and videos. * `getMediaAccept` and `resolveMediaSources` are exported for custom compositions. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. It uses WordPress buttons, menus, dropdowns and the standard `block-editor-media-replace-flow` content classes. ## Gotchas [Section titled “Gotchas”](#gotchas) * Direct URLs do not create WordPress attachments and therefore have no attachment ID. * `upload` controls the visible file-picker button and `dropZone` controls drag-and-drop. Disable both to prohibit every direct file-upload path. * Consumers must escape URLs in PHP and JSX at the final rendering boundary. This control stores a URL; it does not authorize or proxy it. * The featured-image source is disabled until the attachment record resolves. Pass `featuredMedia` when no post editor store exists. ## Related [Section titled “Related”](#related) * [`MediaPickerControl`](/reference/controls/media-picker-control/) * [`MediaCanvasControl`](/reference/controls/media-canvas-control/) * [`MediaToolbarControl`](/reference/controls/media-toolbar-control/) * [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) * [`MediaControl`](/reference/controls/media-control/) # MediaToolbarControl > Adds state-aware select/replace and remove actions to Gutenberg's block toolbar without rendering any block content or inspector UI. ## Summary [Section titled “Summary”](#summary) Adds state-aware select/replace and remove actions to Gutenberg’s block toolbar without rendering any block content or inspector UI. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when media editing belongs only in `BlockControls`. Use `MediaControl` to combine toolbar actions with canvas and sidebar editing. ## Import [Section titled “Import”](#import) ```js import { MediaToolbarControl } from '@isudev/gutenberg/controls'; import { MediaToolbarControl } from '@isudev/gutenberg/controls/MediaToolbarControl'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ----------------------- | ------------------------- | ----------------- | -------- | -------------------------------------------------------------------------- | | `value` | `MediaValue` | `{}` | No | Current serializable media. | | `onChange` | `MediaChangeHandler` | — | Yes | Receives normalized media selections. | | `onRemove` | `() => void` | `onChange( {} )` | No | Custom clearing behavior. | | `actions` | `MediaActionsConfig` | all enabled | No | Independently controls select, replace and remove. | | `sources` | `MediaSourcesConfig` | all enabled | No | Independently controls library, upload, URL, featured image and drop zone. | | `group` | `BlockControlsGroup` | `'other'` | No | Toolbar group receiving the actions. | | `selectLabel` | `string` | `'Select media'` | No | Accessible initial action label. | | `replaceLabel` | `string` | `'Replace media'` | No | Accessible existing-media label. | | `removeLabel` | `string` | `'Remove media'` | No | Accessible clear label. | | `toolbarGroupClassName` | `string` | `undefined` | No | Extra class on `ToolbarGroup`. | | `pickerProps` | `MediaToolbarPickerProps` | `undefined` | No | Native picker options except controlled props. | ## Nested options [Section titled “Nested options”](#nested-options) `actions` accepts `false` to hide the complete toolbar fill, or an object whose fields each default to `true`: | Field | Visible state | Description | | --------- | -------------- | -------------------------------- | | `select` | No media | Shows the initial picker action. | | `replace` | Media selected | Shows the edit/replace action. | | `remove` | Media selected | Shows the remove action. | `sources` is `false` or an object with `library`, `upload`, `url`, `featured` and `dropZone` booleans. `dropZone` has no effect in a toolbar dropdown. `pickerProps` accepts `allowedTypes`, `accept`, `imageSize`, `disabled`, `featuredMedia`, `onFilesUpload`, `onError`, `title`, `modalClass`, `onClose`, `fallback` and `labels` from `MediaSourceControl`. ## Examples [Section titled “Examples”](#examples) ### Toolbar media actions [Section titled “Toolbar media actions”](#toolbar-media-actions) ```jsx setAttributes( { media } ) } /> ``` ### Replace only [Section titled “Replace only”](#replace-only) ```jsx setAttributes( { media } ) } actions={ { select: false, remove: false } } sources={ { upload: false, featured: false } } group="inline" /> ``` ## Behavior [Section titled “Behavior”](#behavior) * With no media, only select can render. With media, it becomes a native-style replacement dropdown containing enabled sources. * Reset is inside the dropdown when replace and remove are both enabled. It remains a standalone toolbar action when replace is disabled. * `actions={ false }` emits no toolbar fill. * The component owns `BlockControls` and `ToolbarGroup`; do not wrap it in another fill. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. WordPress supplies toolbar and icon styles. ## Gotchas [Section titled “Gotchas”](#gotchas) Block toolbar fills display only for the currently selected block. ## Related [Section titled “Related”](#related) * [`MediaPickerControl`](/reference/controls/media-picker-control/) * [`MediaSourceControl`](/reference/controls/media-source-control/) * [`MediaSidebarControl`](/reference/controls/media-sidebar-control/) * [`MediaControl`](/reference/controls/media-control/) # ResponsiveControl > Makes any control responsive: renders a label and a breakpoint switcher, then hands the resolved per-breakpoint value to a render prop. ## Summary [Section titled “Summary”](#summary) Makes any control responsive: renders a label and a breakpoint switcher, then hands the resolved per-breakpoint value to a render prop. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it whenever a block setting should differ per breakpoint. It is the shortest path from a plain `RangeControl` to a responsive one. Do not use it if you need the switcher somewhere other than beside the control — compose `useBreakpoint`, `useResponsiveAttribute` and `BreakpointSwitcher` yourself instead. Do not use it for values that are not stored on block attributes; the hooks are the lower level building block for post meta or custom stores. ## Import [Section titled “Import”](#import) ```js import { ResponsiveControl } from '@isudev/gutenberg/controls'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ---------------- | ---------------------------------------------------- | --------------------- | -------- | --------------------------------------------------------------- | | `attrName` | `string` | — | Yes | Base attribute name, e.g. `'columnGap'`. | | `attributes` | `Record` | — | Yes | The block’s attributes. | | `setAttributes` | `( next: Record ) => void` | — | Yes | The block’s `setAttributes`. | | `children` | `( args: ResponsiveControlRenderArgs ) => ReactNode` | — | Yes | Renders the control with resolved values. | | `label` | `string` | `undefined` | No | Visible label shown beside the switcher. | | `variant` | `'inline' \| 'dropdown'` | `'inline'` | No | Switcher layout. | | `breakpoints` | `Breakpoint[]` | `DEFAULT_BREAKPOINTS` | No | The breakpoint set to offer. | | `syncToEditor` | `boolean` | `false` | No | Push breakpoint changes to the editor’s device preview. | | `syncFromEditor` | `boolean` | `false` | No | Follow the editor’s device preview. | | `showReset` | `boolean` | `true` | No | Show a reset button when the active breakpoint has an override. | | `className` | `string` | `undefined` | No | Extra class name on the root element. | ## Examples [Section titled “Examples”](#examples) ### A responsive range [Section titled “A responsive range”](#a-responsive-range) ```jsx { ( { value, inheritedValue, hasOwnValue, onChange } ) => ( /* * `RangeControl` has no `placeholder` — unknown props are spread onto its * ``, where one is inert. So the slider shows the value that * actually applies and `help` says where it came from. `??`, not `||`: an explicit * `0` is a real override. */ ) } ``` Bind `inheritedValue` to a `placeholder` only on controls that have one — `TextControl` and `InputControl` forward it to their ``: ```jsx { ( { value, inheritedValue, onChange } ) => ( ) } ``` ### Compact switcher, linked to the editor preview [Section titled “Compact switcher, linked to the editor preview”](#compact-switcher-linked-to-the-editor-preview) ```jsx { ( { value, onChange } ) => ( /* * The empty option matters: with no override, `value` is `undefined` and a native * `` and ignored. Reserve that pattern for `TextControl`/`InputControl` and use `value ?? inheritedValue` plus `help` elsewhere. * Attributes must be declared in `block.json` for every breakpoint you offer — `columnGapTablet` and `columnGapMobile` do not spring into existence. * `syncFromEditor` without `syncToEditor` makes the switcher read-only in practice. Clicking a breakpoint selects it, then the effect that follows the editor’s device preview sees an unchanged device type and reverts the selection — nothing pushed the click outwards for it to agree with. Pass both flags for an interactive switcher tied to the preview, or neither for one that stands alone. ## Related [Section titled “Related”](#related) * [`BreakpointSwitcher`](/reference/components/breakpoint-switcher/) — the switcher alone. * [`useResponsiveAttribute`, `useBreakpoint`](/reference/hooks/) — the pieces underneath. * Decision 0003 — why base plus suffixes, and why there is no `default` breakpoint. # Fields > Advanced mode — compose an options source with a value binding. ## Modules [Section titled “Modules”](#modules) | Module | What it does | Narrowest import | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | [`RadioField`](/reference/fields/radio-field/) | A radio-button field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. | `@isudev/gutenberg/fields/RadioField` | | [`SelectField`](/reference/fields/select-field/) | A select field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. | `@isudev/gutenberg/fields/SelectField` | # RadioField > A radio-button field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. ## Summary [Section titled “Summary”](#summary) A radio-button field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when you need a small, always-visible set of choices and the combination of “where the options come from” and “where the value lives” is not one of the built-in easy-mode shapes — for example, options from one taxonomy while the value is written somewhere unrelated. Do not reach for it for the common cases: use `MetaRadioControl` for a radio group bound to post meta. There is no `TaxonomyRadioControl` easy-mode wrapper — compose `optionsSource: { type: 'terms' }` with `valueBinding: { type: 'taxonomy' }` directly, as shown below. Use `SelectField` instead once the option list is long — a dropdown, not a wall of radio buttons, is the better control for a large or dynamic set. ## Import [Section titled “Import”](#import) ```js import { RadioField } from '@isudev/gutenberg/fields'; // or, skipping the barrel: import { RadioField } from '@isudev/gutenberg/fields/RadioField'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------ | ---------------------------- | ------- | -------- | ------------------------------------------------------------------------------------- | | `options` | `FieldOption[]` | — | No | Static list of choices; takes precedence over `optionsSource`. | | `optionsSource` | `OptionsSource` | — | No | Dynamic source for the choices — `terms`, `posts`, `users`, `postTypes`, or `manual`. | | `valueBinding` | `ValueBinding` | — | No | Where the value is read from and written to — `meta`, `taxonomy`, or `custom`. | | `value` | `unknown` | — | No | Controlled value; when present the field is controlled and `valueBinding` is ignored. | | `onChange` | `( value: unknown ) => void` | — | No | Controlled change handler; its presence alone also makes the field controlled. | | `onValueChange` | `( value: unknown ) => void` | — | No | Called after the resolved `onChange` runs, regardless of binding mode. | | `loadingComponent` | `ReactNode` | — | No | Rendered instead of the control while options or the value are resolving. | | `errorComponent` | `ReactNode` | — | No | Rendered instead of the control when resolving fails. | Every `FieldOption` is `{ label: string; value: string | number; disabled?: boolean }`. ## Examples [Section titled “Examples”](#examples) ### Minimal: static options, controlled value [Section titled “Minimal: static options, controlled value”](#minimal-static-options-controlled-value) ```jsx import { useState } from '@wordpress/element'; import { RadioField } from '@isudev/gutenberg/fields'; const [ size, setSize ] = useState( 'medium' ); ``` ### Options from content: terms, posts, and users [Section titled “Options from content: terms, posts, and users”](#options-from-content-terms-posts-and-users) ```jsx // A small taxonomy, ordered alphabetically — value/onChange are still controlled here // to isolate what `optionsSource` alone does. // A handful of landing pages, used as a "redirect to" choice. // Editors and admins only, as a "reviewed by" choice. ``` ### Options from post types, or a manual list [Section titled “Options from post types, or a manual list”](#options-from-post-types-or-a-manual-list) ```jsx // Every public, viewable post type. // A fixed list expressed as a source rather than the `options` prop — useful when a // shared helper builds `optionsSource` generically for every field it configures. ``` ### Value bound to post meta [Section titled “Value bound to post meta”](#value-bound-to-post-meta) ```jsx ``` ### Value bound to taxonomy terms [Section titled “Value bound to taxonomy terms”](#value-bound-to-taxonomy-terms) ```jsx ``` ### Value bound to a custom store [Section titled “Value bound to a custom store”](#value-bound-to-a-custom-store) ```jsx import { useDispatch, useSelect } from '@wordpress/data'; import { RadioField } from '@isudev/gutenberg/fields'; function ThemeModeField() { const themeMode = useSelect( ( select ) => select( 'my-plugin/settings' ).getThemeMode(), [] ); const { setThemeMode } = useDispatch( 'my-plugin/settings' ); return ( ); } ``` ### Loading and error placeholders [Section titled “Loading and error placeholders”](#loading-and-error-placeholders) ```jsx import { Notice, Spinner } from '@wordpress/components'; import { RadioField } from '@isudev/gutenberg/fields'; } errorComponent={ Could not load authors. } /> ``` ### Composition an easy-mode wrapper cannot express [Section titled “Composition an easy-mode wrapper cannot express”](#composition-an-easy-mode-wrapper-cannot-express) ```jsx // Options list every "genre" term, but the pick is written to a separate // "featured-genre" taxonomy used only to drive a homepage query — the post's actual // genre assignment, read elsewhere in the editor, is untouched. ``` There is no easy-mode wrapper for this at all: `MetaRadioControl` only ever writes to meta, and nothing wraps a `taxonomy` binding paired with a different source. `RadioField` lets the options and the value diverge — options from one place, the value written somewhere else entirely. ## Behavior [Section titled “Behavior”](#behavior) * Built on `useFieldBinding`, which composes `useOptionsSource` (from `options`/ `optionsSource`) and `useValueBinding` (from `valueBinding`, or controlled `value`/ `onChange`). The two never interact by design — see `optionsSource !== valueBinding`. * WordPress’s `RadioControl` takes `selected`, not `value`. The field does that renaming for you: consumers of `RadioField` always pass `value`/read `value` back, exactly as with `SelectField`. * `options` beats `optionsSource` outright: if `options` is set, `optionsSource` is never consulted, even when both are passed. * The field is controlled as soon as either `value` or `onChange` is present — not only when both are — and `valueBinding` is then ignored entirely. In development, passing both a `valueBinding` and a controlled prop logs a `console.warn` explaining that the controlled props win. * Whatever the binding mode, the field’s own change handler always runs the resolved writer first (the controlled `onChange`, or the binding’s writer), then calls `onValueChange` (if provided) with the same value — useful for side effects like tracking without taking over the write. * `isLoading` is true while either the options source or the value binding is still resolving; `error` prefers the options source’s error, falling back to the value binding’s. * Per source: `terms`, `posts`, and `users` are loading/erroring based on `core-data`’s `getEntityRecords` resolution for that query; `postTypes` is the same via `getPostTypes`. `manual` and static `options` never load and never error. * Every fetching source (`terms`, `posts`, `users`, `postTypes`) queries with `per_page: -1` by default — the whole collection, not a first page. `query` is merged over that default, so narrow it (`per_page`, `search`, `include`) for anything that can grow: a `users` or unbounded `terms` source will otherwise fetch every record and render one radio button per row. * Per binding: `meta` only reports loading while the post type itself hasn’t resolved yet (not while the meta value is loading), and never reports an error. `taxonomy` never reports loading at all: its REST base falls back to the taxonomy slug synchronously when `getTaxonomy()` hasn’t resolved, so the base is always truthy and `isLoading` is always `false` — whether or not `restBase` was passed. It never reports an error either. `custom` never reports loading or an error — the field trusts whatever is passed. In practice, `errorComponent` only ever fires from a failed options fetch, never from the value side. * While `isLoading` or `error` is true, the field renders `loadingComponent`/ `errorComponent` (or nothing, if omitted) instead of `RadioControl` — the control is not mounted underneath. * Any prop besides the eight above (`label`, `help`, `disabled`, …) is forwarded unchanged to the underlying `RadioControl`. `RadioControl`’s own `disabled` is a single flag for the whole group, and it — like every other pass-through prop — is spread onto **every** individual radio ``, not just the wrapping `
`. * `terms`/`posts`/`users` default `valueField`/`labelField` to `id`/`name` (`title` for posts). `postTypes` ignores both and always uses `slug`/singular name, filtering out non-viewable and internal post types (`attachment`, `wp_block`, `wp_template`, `wp_template_part`, `wp_navigation`, `wp_font_family`, `wp_font_face`). * All four options-source hooks and all three value-binding hooks are called on every render regardless of which type is active (Rules of Hooks) — inactive ones receive `null` and skip fetching, so switching `optionsSource.type` or `valueBinding.type` at runtime is safe. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Renders `@wordpress/components`’ `RadioControl` directly and inherits its editor chrome; there are no custom properties to override. ## Gotchas [Section titled “Gotchas”](#gotchas) * `RadioControl` matches the checked option with strict equality (`option.value === selected`) and its `onChange` always hands back the string value of the chosen `` (`event.target.value`). If `optionsSource` resolves numeric values (the default `valueField: 'id'` for `terms`/`posts`/`users`) and that value round-trips back in as `value`, **no option will show as checked** — `5 === '5'` is `false`. Use `valueField: 'slug'` (or another string field), or keep the numeric id consistently as a number on the way back in. * A `taxonomy` binding reads and writes the entity property named after the taxonomy’s REST base, and until `getTaxonomy()` resolves it falls back to the taxonomy **slug**. Where the two differ — core’s `category` is exposed as `categories`, `post_tag` as `tags` — the first renders read `undefined` from a property that does not exist, and a write inside that window goes to that non-existent property. There is no loading flag covering it, so nothing surfaces the problem. Pass `restBase` explicitly (`{ type: 'taxonomy', taxonomy: 'category', restBase: 'categories' }`) for any taxonomy whose REST base isn’t identical to its name. * Per-option `disabled` on a `FieldOption` has no effect here: `RadioControl` only supports `label`, `value`, and `description` per option and does not read a per-option `disabled` flag. Disable the whole group instead by forwarding a top-level `disabled` prop. * Passing only `onChange` without `value` (or the reverse) is enough to switch the field to controlled mode and silently drop `valueBinding` — pass both, or neither. * If both `options` and `optionsSource` are set, `optionsSource` is ignored outright rather than merged with it — remove `options` once a field moves to a dynamic source. * An options list that resolves empty (no matching terms/posts/users, or a query that matches nothing) renders nothing: `RadioControl` returns `null` for an empty `options` array, so there is no visible “no results” state. * `optionsSource: { type: 'postTypes' }` has no `valueField`/`labelField` — check the `OptionsSource` type before assuming every source supports them. ## Related [Section titled “Related”](#related) * [`SelectField`](/reference/fields/select-field/) — same engine, a dropdown instead of radio buttons. * [`MetaRadioControl`](/reference/meta/meta-radio-control/) — easy mode for a radio group bound to post meta. * [`TaxonomySelectControl`](/reference/taxonomy/taxonomy-select-control/) — the equivalent easy mode for taxonomy-bound options and value, as a dropdown. * [`useCurrentPostType`](/reference/hooks/use-current-post-type/) — what a `meta`/ `taxonomy` binding falls back to when `postType` is omitted. # SelectField > A select field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. ## Summary [Section titled “Summary”](#summary) A select field that composes an options source and a value binding independently — pick a static list or a dynamic source for the choices, and separately bind the value to post meta, a taxonomy, or a custom store. ## When to use / When not to use [Section titled “When to use / When not to use”](#when-to-use--when-not-to-use) Use it when you need a dropdown and the combination of “where the options come from” and “where the value lives” is not one of the built-in easy-mode shapes — for example, options from one taxonomy while the value is written somewhere unrelated. Do not reach for it for the common cases: use `MetaSelectControl` for a select bound to post meta, or `TaxonomySelectControl` for a select whose options and value are both a single taxonomy’s terms. Use `RadioField` instead when a small, always-visible set of choices reads better as radio buttons than as a dropdown. ## Import [Section titled “Import”](#import) ```js import { SelectField } from '@isudev/gutenberg/fields'; // or, skipping the barrel: import { SelectField } from '@isudev/gutenberg/fields/SelectField'; ``` ## Props [Section titled “Props”](#props) | Name | Type | Default | Required | Description | | ------------------ | ---------------------------- | ------- | -------- | ------------------------------------------------------------------------------------- | | `options` | `FieldOption[]` | — | No | Static list of choices; takes precedence over `optionsSource`. | | `optionsSource` | `OptionsSource` | — | No | Dynamic source for the choices — `terms`, `posts`, `users`, `postTypes`, or `manual`. | | `valueBinding` | `ValueBinding` | — | No | Where the value is read from and written to — `meta`, `taxonomy`, or `custom`. | | `value` | `unknown` | — | No | Controlled value; when present the field is controlled and `valueBinding` is ignored. | | `onChange` | `( value: unknown ) => void` | — | No | Controlled change handler; its presence alone also makes the field controlled. | | `onValueChange` | `( value: unknown ) => void` | — | No | Called after the resolved `onChange` runs, regardless of binding mode. | | `loadingComponent` | `ReactNode` | — | No | Rendered instead of the control while options or the value are resolving. | | `errorComponent` | `ReactNode` | — | No | Rendered instead of the control when resolving fails. | Every `FieldOption` is `{ label: string; value: string | number; disabled?: boolean }`. ## Examples [Section titled “Examples”](#examples) ### Minimal: static options, controlled value [Section titled “Minimal: static options, controlled value”](#minimal-static-options-controlled-value) ```jsx import { useState } from '@wordpress/element'; import { SelectField } from '@isudev/gutenberg/fields'; const [ size, setSize ] = useState( 'medium' ); ``` ### Options from content: terms, posts, and users [Section titled “Options from content: terms, posts, and users”](#options-from-content-terms-posts-and-users) ```jsx // Categories, ordered by post count — value/onChange are still controlled here to // isolate what `optionsSource` alone does. // Pages, used as a "related page" picker. // Editors and admins only, as an "assigned to" picker. ``` ### Options from post types, or a manual list [Section titled “Options from post types, or a manual list”](#options-from-post-types-or-a-manual-list) ```jsx // Every public, viewable post type. // A fixed list expressed as a source rather than the `options` prop — useful when a // shared helper builds `optionsSource` generically for every field it configures. ``` ### Value bound to post meta [Section titled “Value bound to post meta”](#value-bound-to-post-meta) ```jsx ``` ### Value bound to taxonomy terms [Section titled “Value bound to taxonomy terms”](#value-bound-to-taxonomy-terms) ```jsx ``` ### Value bound to a custom store [Section titled “Value bound to a custom store”](#value-bound-to-a-custom-store) ```jsx import { useDispatch, useSelect } from '@wordpress/data'; import { SelectField } from '@isudev/gutenberg/fields'; function ThemeColorField() { const themeColor = useSelect( ( select ) => select( 'my-plugin/settings' ).getThemeColor(), [] ); const { setThemeColor } = useDispatch( 'my-plugin/settings' ); return ( ); } ``` ### Loading and error placeholders [Section titled “Loading and error placeholders”](#loading-and-error-placeholders) ```jsx import { Notice, Spinner } from '@wordpress/components'; import { SelectField } from '@isudev/gutenberg/fields'; } errorComponent={ Could not load authors. } /> ``` ### Composition an easy-mode wrapper cannot express [Section titled “Composition an easy-mode wrapper cannot express”](#composition-an-easy-mode-wrapper-cannot-express) ```jsx // Options list every "genre" term, but the pick is written to a separate // "featured-genre" taxonomy used only to drive a homepage query — the post's actual // genre assignment, read elsewhere in the editor, is untouched. ``` `TaxonomySelectControl` cannot do this: it takes a single `taxonomy` prop and locks the options and the value to that same taxonomy. `SelectField` lets the two diverge — options from one place, the value written somewhere else entirely. ## Behavior [Section titled “Behavior”](#behavior) * Built on `useFieldBinding`, which composes `useOptionsSource` (from `options`/ `optionsSource`) and `useValueBinding` (from `valueBinding`, or controlled `value`/ `onChange`). The two never interact by design — see `optionsSource !== valueBinding`. * `options` beats `optionsSource` outright: if `options` is set, `optionsSource` is never consulted, even when both are passed. * The field is controlled as soon as either `value` or `onChange` is present — not only when both are — and `valueBinding` is then ignored entirely. In development, passing both a `valueBinding` and a controlled prop logs a `console.warn` explaining that the controlled props win. * Whatever the binding mode, the field’s own change handler always runs the resolved writer first (the controlled `onChange`, or the binding’s writer), then calls `onValueChange` (if provided) with the same value — useful for side effects like tracking without taking over the write. * `isLoading` is true while either the options source or the value binding is still resolving; `error` prefers the options source’s error, falling back to the value binding’s. * Per source: `terms`, `posts`, and `users` are loading/erroring based on `core-data`’s `getEntityRecords` resolution for that query; `postTypes` is the same via `getPostTypes`. `manual` and static `options` never load and never error. * Every fetching source (`terms`, `posts`, `users`, `postTypes`) queries with `per_page: -1` by default — the whole collection, not a first page. `query` is merged over that default, so narrow it (`per_page`, `search`, `include`) for anything that can grow; the source for `posts` flags this in its own comments as the reason a searchable mode would be needed for large datasets. * Per binding: `meta` only reports loading while the post type itself hasn’t resolved yet (not while the meta value is loading), and never reports an error. `taxonomy` never reports loading at all: its REST base falls back to the taxonomy slug synchronously when `getTaxonomy()` hasn’t resolved, so the base is always truthy and `isLoading` is always `false` — whether or not `restBase` was passed. It never reports an error either. `custom` never reports loading or an error — the field trusts whatever is passed. In practice, `errorComponent` only ever fires from a failed options fetch, never from the value side. * While `isLoading` or `error` is true, the field renders `loadingComponent`/ `errorComponent` (or nothing, if omitted) instead of `SelectControl` — the control is not mounted underneath. * Any prop besides the eight above (`label`, `help`, `disabled`, `multiple`, …) is forwarded unchanged to the underlying `SelectControl`. * `terms`/`posts`/`users` default `valueField`/`labelField` to `id`/`name` (`title` for posts). `postTypes` ignores both and always uses `slug`/singular name, filtering out non-viewable and internal post types (`attachment`, `wp_block`, `wp_template`, `wp_template_part`, `wp_navigation`, `wp_font_family`, `wp_font_face`). * All four options-source hooks and all three value-binding hooks are called on every render regardless of which type is active (Rules of Hooks) — inactive ones receive `null` and skip fetching, so switching `optionsSource.type` or `valueBinding.type` at runtime is safe. ## Styling [Section titled “Styling”](#styling) Ships no stylesheet. Renders `@wordpress/components`’ `SelectControl` directly and inherits its editor chrome; there are no custom properties to override. ## Gotchas [Section titled “Gotchas”](#gotchas) * `SelectControl` always hands back the string value of the chosen `