Skip to content

Allow Core blocks to resolve internal UI SVGs through the Icon Registry #81225

Open

Description

@Jiwoon-Kim

What problem does this address?

WordPress now has an Icon Registry and the core/icon block can render a named
icon from it. However, many Core blocks still render their internal UI icons as
hard-coded inline SVG markup.

This makes theme-level icon customization fragile.

My block theme, Axismundi, uses
Material Symbols as its icon language. To make Core block controls visually
consistent, the theme currently has to hide Core SVGs and recreate them with
CSS generated content and an icon font.

Examples from the theme:

  1. Search block submit icon

    The Search block outputs its own SVG. The theme hides it and inserts the
    Material Symbols search glyph with ::before.

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/theme.json#L1088

  2. Top-level Navigation submenu disclosure

    The Navigation block outputs an SVG disclosure icon. The theme hides it and
    substitutes arrow_drop_down, changing to arrow_drop_up when expanded.

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/assets/styles/blocks.navigation.css#L405

  3. Nested Navigation submenu disclosure

    Nested menu rows have another internal Core SVG. The theme hides it and
    substitutes Material Symbols arrow_right.

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/assets/styles/blocks.navigation-submenu.css#L168

  4. Responsive Navigation menu trigger

    icon: "menu" renders a three-line Core SVG, but does not provide a
    semantic class or identifier for that variant. The theme currently identifies
    it by its SVG path data, hides the SVG, and inserts a Material Symbols
    menu glyph.

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/assets/styles/blocks.navigation.css#L494

    The selector depends on this implementation detail:

    :has(svg path[d="M5 5v1.5h14V5H5z"])

    A harmless Core SVG path update could silently break the theme's menu icon.

  5. Image block Lightbox controls

    Core Lightbox close, previous, and next SVGs are hidden and replaced with
    Material Symbols close, chevron_left, and chevron_right.

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/style.css#L325

These workarounds also require themes to preserve Core behavior manually:
control sizing, state changes, hover/focus/active styles, RTL behavior,
accessibility, responsive behavior, and editor/frontend parity.

The problem is not simply styling an SVG. It is that a theme must replace a
semantic Core UI icon through private markup details.

Theme:
https://wordpress.org/themes/axismundi/

Source:
https://github.com/Jiwoon-Kim/axismundi/tree/main/products/distributables/themes/axismundi

What is your proposed solution?

Core blocks should resolve their built-in UI icons through the WordPress Icon
Registry instead of embedding glyph-specific SVG markup directly in each block.

The important requirement is a stable semantic icon identifier. Core should
continue to provide its existing SVGs as defaults, but themes should be able to
replace their visual implementation through the registry.

For example, Core could expose identifiers conceptually similar to:

  • core/search
  • core/navigation-menu
  • core/navigation-submenu
  • core/navigation-submenu-nested
  • core/close
  • core/chevron-left
  • core/chevron-right

The exact names and API shape are less important than the outcome:

  • Core retains its default icon set.
  • Blocks preserve their existing button markup, labels, accessibility, layout,
    state handling, and responsive behavior.
  • Themes can replace a glyph by registering an SVG for a semantic identifier.
  • The same mechanism works in both the editor and frontend.
  • Themes no longer need to hide SVGs, recreate them with ::before, use icon
    fonts, or select controls through SVG path data.

This would let the existing Icon Registry become a genuine theming seam for
Core block UI, rather than being limited primarily to explicit core/icon
content blocks.


P.S. I have added a deliberately small experiment to my theme: it replaces only
the registered core/menu and core/search icons with local Material Symbols
SVGs, and only when the Icon API is available.

https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/inc/core-icon-overrides.php

This works for consumers of the Icon API, including explicit core/icon
blocks. It does not affect the Navigation menu button or Search submit button
today, because those blocks still render their own inline SVG markup rather
than resolving the registered icon.

That contrast is the concrete reason for this request: the registry is already
a viable theming seam, but Core block UI icons do not consistently use it yet.

Activity

  1. Jiwoon-Kim commented on Aug 5, 2026

    @Jiwoon-Kim
    ContributorAuthor

    I did a frontend-rendering scan against Gutenberg 23.6.2 to make this more
    concrete and to identify a possible implementation path.

    Current frontend SVG emitters

    When the Gutenberg plugin is active, it re-registers Core block types from its
    own built block-library implementation:

    lib/blocks.php → gutenberg_reregister_core_block_types()

    The corresponding source lives under:

    packages/block-library/src/

    The following are the current server-side frontend SVG emitters relevant to
    this proposal.

    Block / UI surface Source location Current output
    Navigation responsive open / close controls packages/block-library/src/navigation/index.php Literal SVG strings for the default menu handle, the icon: "menu" variant, and close
    Navigation submenu disclosure packages/block-library/src/navigation-link/shared/render-submenu-icon.php Literal 12px chevron SVG
    Navigation Overlay Close packages/block-library/src/navigation-overlay-close/index.php Literal close SVG
    Page List submenu disclosure packages/block-library/src/page-list/index.php Literal submenu chevron SVG, duplicating the Navigation affordance
    Search icon-only submit packages/block-library/src/search/index.php Literal search SVG
    Image Lightbox packages/block-library/src/image/index.php Literal fullscreen, close, previous, and next SVGs

    The relevant current source examples are:

    I would keep core/social-link out of an initial implementation. Its inline
    SVGs are service/brand marks rather than generic Core UI controls, and they
    need a different compatibility discussion.

    Existing Icon Registry seam

    core/icon already establishes the desired rendering pattern:

    $svg = wp_get_icon( $attributes['icon'], $args );

    Source:

    https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/icon/index.php#L74-L82

    The Gutenberg compatibility layer currently registers the core collection
    and loads its default icon manifest from packages/icons/src/:

    packages/icons/src/manifest.php
        -> wp_register_icon( 'core/' . $icon_name, ... )
        -> wp_get_icon( $name, $args )
    

    This is already sufficient for Core itself to obtain an SVG with consistent
    size, class, and accessibility handling.

    Suggested incremental implementation

    I think this can be implemented in small, independently reviewable steps.

    1. Add stable semantic identifiers for block UI icons

    The first step is not to expose every existing SVG path as an API. It is to
    give each UI role a stable semantic identifier and register Core's current SVG
    as that identifier's default.

    The exact names need discussion, but they should be specific enough not to
    change the meaning of existing core/icon content. For example:

    core/navigation-menu-default
    core/navigation-menu
    core/navigation-close
    core/navigation-submenu
    core/navigation-submenu-nested
    core/search-submit
    core/lightbox-close
    core/lightbox-previous
    core/lightbox-next
    core/lightbox-fullscreen
    

    I would prefer dedicated UI names over reusing a generic content-facing name
    such as core/menu. A theme may reasonably want to customize a Navigation
    control without also changing every explicit <!-- wp:icon {"icon":"core/menu"} /-->
    instance stored in content.

    2. Replace literal SVG strings in block renderers with wp_get_icon()

    For example, Navigation currently has this shape:

    $toggle_button_icon = '<svg ...>...</svg>';
    
    if ( 'menu' === $attributes['icon'] ) {
        $toggle_button_icon = '<svg ...>...</svg>';
    }

    It could become conceptually:

    $toggle_button_icon = wp_get_icon(
        'core/navigation-menu-default',
        array( 'size' => 24 )
    );
    
    if ( 'menu' === $attributes['icon'] ) {
        $toggle_button_icon = wp_get_icon(
            'core/navigation-menu',
            array( 'size' => 24 )
        );
    }

    The same narrow substitution applies to:

    • block_core_shared_navigation_render_submenu_icon();
    • render_block_core_navigation_overlay_close();
    • the duplicated Page List submenu chevron;
    • the Search render callback;
    • Image Lightbox control variables.

    This preserves all existing wrapper markup, aria-label ownership,
    Interactivity API directives, and responsive behavior. Only the source of the
    inner SVG changes.

    3. Deduplicate existing Core UI icon call sites

    There is already an obvious first cleanup opportunity:

    • Navigation has a shared submenu icon helper.
    • Page List currently emits an equivalent literal chevron separately.

    Page List should consume the same semantic icon helper rather than carrying a
    second SVG string. This gives the proposed identifier a second real consumer
    immediately.

    4. Define a supported theme override mechanism separately

    The current registry documentation reserves the core collection for Core
    icons. Therefore a theme should not be expected to unregister and re-register
    core/* entries, even though that can be used experimentally today.

    The block refactor and official theme override semantics can be separate
    changes:

    1. Core blocks first resolve their own UI icons through stable Core registry
      identifiers.
    2. A follow-up defines the supported extension point for themes, for example a
      resolver/filter that can map a Core UI identifier to a theme collection
      identifier, or a controlled icon-render filter.

    This avoids requiring third-party themes to mutate the reserved core
    collection while still making the registry a genuine theming seam.

    A resolver conceptually like the following would be enough:

    $icon_name = apply_filters(
        'wp_resolve_block_ui_icon',
        'core/navigation-menu',
        array(
            'block' => 'core/navigation',
            'role'  => 'responsive-open',
        )
    );
    
    return wp_get_icon( $icon_name, $args );

    The exact hook name and arguments are only illustrative. The key requirement
    is that a theme can substitute its own registered SVG without parsing or
    replacing rendered HTML.

    5. Test at the render boundary

    Tests should verify more than the default SVG output. A useful integration
    test would register a temporary substitute for a UI identifier through the
    supported resolver, render the relevant block with do_blocks(), and assert
    that the substitute SVG appears while the existing button markup and
    accessibility attributes remain intact.

    Initial coverage should include:

    • Navigation default trigger and icon: "menu" trigger;
    • Navigation submenu;
    • Page List submenu;
    • Navigation Overlay Close;
    • Search icon-only button;
    • Image Lightbox close / previous / next.

    Why this is useful even before every block migrates

    A partial migration is already valuable. Search, Navigation, and Image
    Lightbox are sufficient to remove several fragile theme workarounds, including
    CSS selectors coupled to private SVG path data.

    For reference, this small theme experiment demonstrates that the existing
    registry already works for explicit core/icon consumers:

    https://github.com/Jiwoon-Kim/axismundi/blob/main/products/distributables/themes/axismundi/inc/core-icon-overrides.php

    It intentionally does not affect the Navigation or Search controls today,
    because those renderers still emit their own SVG strings. That is the gap this
    proposal addresses.

  2. Vrishabhsk commented on Aug 26, 2026

    @Vrishabhsk
    Contributor

    Hey @Jiwoon-Kim ! I've opened a PR

    Core blocks now resolve their internal UI icons through the WordPress Icon Registry instead of hardcoding inline SVGs. Themes can override any block UI icon by registering a replacement SVG for a stable semantic identifier. Core retains its exact existing SVGs as defaults — no visual regression.

    Exposed icon identifiers:

    Block UI element Icon ID
    Search submit button core/search
    Navigation submenu chevron (top-level & nested) core/navigation-submenu
    Navigation responsive toggle (default two bars) core/navigation-menu-toggle
    Navigation responsive toggle (menu variant three bars) core/menu
    Navigation & Image lightbox close core/close
    Image lightbox previous / next core/chevron-left / core/chevron-right

    Theme override example:

    add_action( 'init', function () {
        wp_unregister_icon( 'core/search' );
        wp_register_icon( 'core/search', array(
            'label'   => 'Search',
            'content' => '<svg viewBox="0 0 24 24"><path d="..." /></svg>',
        ) );
    }, 20 );

    Works in both editor and frontend. The default SVGs exactly match the originals, so themes like Axismundi can now replace glyphs through the registry instead of hiding SVGs and recreating them with ::before / icon fonts / path-data selectors.

    Would love feedback on the icon naming and the approach!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions