Skip to content

Add experiment to replace dashicons in admin bar and menu with @wordpress/icons - #79588

Draft
fushar wants to merge 1 commit into
WordPress:trunkfrom
fushar:wp-icons-experiment
Draft

Add experiment to replace dashicons in admin bar and menu with @wordpress/icons#79588
fushar wants to merge 1 commit into
WordPress:trunkfrom
fushar:wp-icons-experiment

Conversation

@fushar

@fushar fushar commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What?

Adds a new experiment, "@wordpress/icons in WP Admin" (gutenberg-admin-wp-icons), that replaces the dashicons in the admin bar and the admin menu with SVG icons from the @wordpress/icons library.

Original Core PR: WordPress/wordpress-develop#12270.

@fushar fushar changed the title @wordpress/icons in WP Admin (experiment) Add experiment to replace dashicons in admin bar and menu with @wordpress/icons Jun 26, 2026
@fushar
fushar force-pushed the wp-icons-experiment branch from 54e0a50 to 7e3b0df Compare June 26, 2026 13:55
Replace the dashicons in the admin bar and admin menu with SVG icons from
the @wordpress/icons library, behind the `gutenberg-admin-wp-icons`
experiment. Ports wordpress-develop#12270 as a plugin experiment.

- Admin bar: rewrites the toolbar node titles server-side on `admin_bar_menu`.
- Admin menu: rewrites the rendered menu markup via an output buffer, since
  `menu-header.php` exposes no per-icon hook.
- Icons are read directly from the library SVG files (not `wp_get_icon()`), so
  they are not registered publicly and stay out of the Icon block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@mcsf mcsf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cool effort. :)

Just for fun, I put together a basic iteration on the Icons registry based on conversations with @t-hamano to allow the registry to distinguish between public and non-public icons — the latter being available only to direct consumers of WP_Icons_Registry and not the REST endpoint. Note the changes in packages/icons/src/manifest.json to the last icon (wordpress).

(You don't have to apply these changes here, btw. I think it's fine to define gutenberg_admin_wp_icons_svg for the purpose of getting the experiment started.)

Details
commit 4518572ca5dc5e76a93d10d3683039687317db76
Author: Miguel Fonseca <mcsf@git.wordpress.org>
Date:   Fri Jun 26 16:16:47 2026 +0100

    PoC: Expose non-public icons to wp_get_icon but not REST
    
    Try it yourself:
    
    - See manifest.json: add `public: true` to more icons needed for the
      admin experiment.
    
    - Run `npm run -w packages/icons build` to update manifest.php.
    
    How:
    
    - Extend WP_Icons_Registry_Gutenberg to add a `public` flag
    - Extend WP_REST_Icons_Controller_Gutenberg to restrict results to
      public icons.

diff --git a/lib/class-wp-icons-registry-gutenberg.php b/lib/class-wp-icons-registry-gutenberg.php
index 8ed3d5502a9..fbf63aa92c1 100644
--- a/lib/class-wp-icons-registry-gutenberg.php
+++ b/lib/class-wp-icons-registry-gutenberg.php
@@ -44,6 +44,7 @@ protected function __construct() {
 				'core/' . $icon_name,
 				array(
 					'label'     => $icon_data['label'],
+					'public'    => $icon_data['public'] ?? false,
 					'file_path' => $icons_directory . $icon_data['filePath'],
 				)
 			);
@@ -62,6 +63,7 @@ protected function __construct() {
 	 *                            If not provided, the content will be retrieved from the `file_path` if set.
 	 *                            If both `content` and `file_path` are not set, the icon will not be registered.
 	 *     @type string $file_path Optional. The full path to the file containing the icon content.
+	 *     @type bool   $public   Optional. Whether the icon should be accessible via REST endpoint.
 	 * }
 	 * @return bool True if the icon was registered with success and false otherwise.
 	 */
@@ -103,7 +105,7 @@ protected function register( $icon_name, $icon_properties ) {
 			return false;
 		}
 
-		$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 );
+		$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'public' ), 1 );
 		foreach ( array_keys( $icon_properties ) as $key ) {
 			if ( ! array_key_exists( $key, $allowed_keys ) ) {
 				_doing_it_wrong(
@@ -161,6 +163,15 @@ protected function register( $icon_name, $icon_properties ) {
 			}
 		}
 
+		if ( ! is_bool( $icon_properties['public'] ) ) {
+			_doing_it_wrong(
+				__METHOD__,
+				__( 'Icon `public` flag must be a boolean.', 'gutenberg' ),
+				'7.1.0'
+			);
+			return false;
+		}
+
 		$icon = array_merge(
 			$icon_properties,
 			array( 'name' => $icon_name )
@@ -255,8 +266,11 @@ function gutenberg_override_wp_icons_registry() {
 	$original_registry  = $property->getValue( null );
 	$gutenberg_registry = WP_Icons_Registry_Gutenberg::get_instance();
 
-	// If the original registry was already instantiated, replay any icons outside
-	// the `core/` namespace onto the Gutenberg registry so they are not lost.
+	// If the original registry was already instantiated, replay any icons onto
+	// the Gutenberg registry so they are not lost.
+	//
+	// Don't exclude core icons, as the Gutenberg registry adds a new `public`
+	// flag.
 	if ( null !== $original_registry ) {
 		$register_method = new ReflectionMethod( WP_Icons_Registry_Gutenberg::class, 'register' );
 		/*
@@ -269,10 +283,8 @@ function gutenberg_override_wp_icons_registry() {
 			$register_method->setAccessible( true );
 		}
 		foreach ( $original_registry->get_registered_icons() as $icon ) {
-			if ( strpos( $icon['name'], 'core/' ) === 0 ) {
-				continue;
-			}
-			$icon_properties = array( 'label' => $icon['label'] );
+			$icon_properties           = array( 'label' => $icon['label'] );
+			$icon_properties['public'] = $icon['public'] ?? false;
 			if ( ! empty( $icon['content'] ) ) {
 				$icon_properties['content'] = $icon['content'];
 			} elseif ( ! empty( $icon['file_path'] ) ) {
diff --git a/lib/class-wp-rest-icons-controller-gutenberg.php b/lib/class-wp-rest-icons-controller-gutenberg.php
index e20c5a39e19..3dc6c939899 100644
--- a/lib/class-wp-rest-icons-controller-gutenberg.php
+++ b/lib/class-wp-rest-icons-controller-gutenberg.php
@@ -15,4 +15,35 @@
  * @since 7.1.0
  */
 class WP_REST_Icons_Controller_Gutenberg extends WP_REST_Icons_Controller {
+	public function get_items( $request ) {
+		$response = array();
+		$search   = $request->get_param( 'search' );
+		$icons    = WP_Icons_Registry::get_instance()->get_registered_icons( $search );
+		foreach ( $icons as $icon ) {
+			if ( $icon['public'] ) {
+				$prepared_icon = $this->prepare_item_for_response( $icon, $request );
+				$response[]    = $this->prepare_response_for_collection( $prepared_icon );
+			}
+		}
+		return rest_ensure_response( $response );
+	}
+
+	public function get_icon( $name ) {
+		$registry = WP_Icons_Registry::get_instance();
+		$icon     = $registry->get_registered_icon( $name );
+
+		if ( null === $icon || empty( $icon['public'] ) ) {
+			return new WP_Error(
+				'rest_icon_not_found',
+				sprintf(
+					// translators: %s is the name of any user-provided name
+					__( 'Icon not found: "%s".', 'gutenberg' ),
+					$name
+				),
+				array( 'status' => 404 )
+			);
+		}
+
+		return $icon;
+	}
 }
diff --git a/lib/experimental/admin-wp-icons/admin-bar.php b/lib/experimental/admin-wp-icons/admin-bar.php
index 51c9c69c7fa..487747a6a19 100644
--- a/lib/experimental/admin-wp-icons/admin-bar.php
+++ b/lib/experimental/admin-wp-icons/admin-bar.php
@@ -9,7 +9,7 @@
  * Returns an admin bar icon wrapper containing the inline SVG for a core icon.
  */
 function gutenberg_admin_wp_icons_ab_icon( $icon_name ) {
-	$svg = gutenberg_admin_wp_icons_svg( $icon_name );
+	$svg = wp_get_icon( $icon_name );
 	if ( '' === $svg ) {
 		return '';
 	}
@@ -39,7 +39,7 @@ function gutenberg_admin_wp_icons_admin_bar( $wp_admin_bar ) {
 
 	$placeholder    = '<span class="ab-icon" aria-hidden="true"></span>';
 	$blavatar_empty = '<div class="blavatar"></div>';
-	$blavatar_icon  = gutenberg_admin_wp_icons_svg( 'core/wordpress', 16 );
+	$blavatar_icon  = wp_get_icon( 'core/wordpress', array( 'size' => 16 ) );
 
 	foreach ( $wp_admin_bar->get_nodes() as $id => $node ) {
 		$title = $node->title;
diff --git a/lib/experimental/admin-wp-icons/admin-menu.php b/lib/experimental/admin-wp-icons/admin-menu.php
index ac0dd52ba48..b5fd4d03266 100644
--- a/lib/experimental/admin-wp-icons/admin-menu.php
+++ b/lib/experimental/admin-wp-icons/admin-menu.php
@@ -37,7 +37,7 @@ static function ( $matches ) use ( $map ) {
 			if ( ! isset( $map[ $matches[2] ] ) ) {
 				return $matches[0];
 			}
-			$svg = gutenberg_admin_wp_icons_svg( $map[ $matches[2] ] );
+			$svg = wp_get_icon( $map[ $matches[2] ] );
 			if ( '' === $svg ) {
 				return $matches[0];
 			}
@@ -46,7 +46,7 @@ static function ( $matches ) use ( $map ) {
 		$html
 	);
 
-	$chevron = gutenberg_admin_wp_icons_svg( 'core/chevron-left' );
+	$chevron = wp_get_icon( 'core/chevron-left' );
 	if ( '' !== $chevron ) {
 		$html = str_replace(
 			'<span class="collapse-button-icon" aria-hidden="true"></span>',
diff --git a/lib/experimental/admin-wp-icons/load.php b/lib/experimental/admin-wp-icons/load.php
index acfac7c82ea..da3db27d416 100644
--- a/lib/experimental/admin-wp-icons/load.php
+++ b/lib/experimental/admin-wp-icons/load.php
@@ -11,33 +11,3 @@
 function gutenberg_admin_wp_icons_enabled() {
 	return gutenberg_is_experiment_enabled( 'gutenberg-admin-wp-icons' );
 }
-
-/**
- * Returns inline SVG markup for a library icon.
- *
- * Implementation adapted from `wp_get_icon()`, but reads the SVG from its library file instead of the
- * icon registry so we can render icons that are not publicly registered.
- */
-function gutenberg_admin_wp_icons_svg( $icon_name, $size = 22 ) {
-	$slug = str_starts_with( $icon_name, 'core/' ) ? substr( $icon_name, 5 ) : $icon_name;
-	if ( ! preg_match( '/^[a-z0-9-]+$/', $slug ) ) {
-		return '';
-	}
-
-	$path = gutenberg_dir_path() . 'packages/icons/src/library/' . $slug . '.svg';
-	if ( ! is_readable( $path ) ) {
-		return '';
-	}
-
-	$processor = new WP_HTML_Tag_Processor( file_get_contents( $path ) );
-	if ( ! $processor->next_tag( 'svg' ) ) {
-		return '';
-	}
-
-	$processor->set_attribute( 'width', (string) $size );
-	$processor->set_attribute( 'height', (string) $size );
-	$processor->set_attribute( 'aria-hidden', 'true' );
-	$processor->set_attribute( 'focusable', 'false' );
-
-	return $processor->get_updated_html();
-}
diff --git a/packages/icons/lib/generate-manifest-php.cjs b/packages/icons/lib/generate-manifest-php.cjs
index 8d7efee06f0..644068f0802 100644
--- a/packages/icons/lib/generate-manifest-php.cjs
+++ b/packages/icons/lib/generate-manifest-php.cjs
@@ -45,10 +45,12 @@ function generatePHPArray( manifest ) {
 		const key = formatPHPKey( item.slug, maxKeyLength );
 		const label = escapePHPString( item.label );
 		const filePath = escapePHPString( item.filePath );
+		const isPublic = item.public ? 'true' : 'false';
 
 		return `${ key } => array(
 		'label'    => _x( '${ label }', 'icon label', 'gutenberg' ),
 		'filePath' => '${ filePath }',
+		'public'   => ${ isPublic },
 	),`;
 	} );
 
@@ -64,7 +66,9 @@ ${ phpEntries.join( '\n' ) }
 async function generateManifestPHP() {
 	const manifestJson = await readFile( MANIFEST_JSON_PATH, 'utf8' );
 	const manifest = JSON.parse( manifestJson );
-	const publicIcons = manifest.filter( ( item ) => item.public === true );
+	const publicIcons = manifest.filter(
+		( item ) => item.public !== undefined
+	);
 	const phpHeader = `<?php
 // This file is automatically generated. Do not edit directly.
 if ( ! defined( 'ABSPATH' ) ) {
diff --git a/packages/icons/src/manifest.json b/packages/icons/src/manifest.json
index f8f28abdc24..a7034bfb6fe 100644
--- a/packages/icons/src/manifest.json
+++ b/packages/icons/src/manifest.json
@@ -1765,6 +1765,7 @@
 	{
 		"slug": "wordpress",
 		"label": "WordPress",
-		"filePath": "library/wordpress.svg"
+		"filePath": "library/wordpress.svg",
+		"public": false
 	}
 ]
diff --git a/packages/icons/src/manifest.php b/packages/icons/src/manifest.php
index d2eb04cff14..1d0893c3a2a 100644
--- a/packages/icons/src/manifest.php
+++ b/packages/icons/src/manifest.php
@@ -8,353 +8,446 @@
 	'arrow-down-left'     => array(
 		'label'    => _x( 'Arrow Down Left', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-down-left.svg',
+		'public'   => true,
 	),
 	'arrow-down-right'    => array(
 		'label'    => _x( 'Arrow Down Right', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-down-right.svg',
+		'public'   => true,
 	),
 	'arrow-down'          => array(
 		'label'    => _x( 'Arrow Down', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-down.svg',
+		'public'   => true,
 	),
 	'arrow-left'          => array(
 		'label'    => _x( 'Arrow Left', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-left.svg',
+		'public'   => true,
 	),
 	'arrow-right'         => array(
 		'label'    => _x( 'Arrow Right', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-right.svg',
+		'public'   => true,
 	),
 	'arrow-up-left'       => array(
 		'label'    => _x( 'Arrow Up Left', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-up-left.svg',
+		'public'   => true,
 	),
 	'arrow-up-right'      => array(
 		'label'    => _x( 'Arrow Up Right', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-up-right.svg',
+		'public'   => true,
 	),
 	'arrow-up'            => array(
 		'label'    => _x( 'Arrow Up', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/arrow-up.svg',
+		'public'   => true,
 	),
 	'at-symbol'           => array(
 		'label'    => _x( 'At Symbol (@)', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/at-symbol.svg',
+		'public'   => true,
 	),
 	'audio'               => array(
 		'label'    => _x( 'Audio', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/audio.svg',
+		'public'   => true,
 	),
 	'bell'                => array(
 		'label'    => _x( 'Bell', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/bell.svg',
+		'public'   => true,
 	),
 	'block-default'       => array(
 		'label'    => _x( 'Block Default', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/block-default.svg',
+		'public'   => true,
 	),
 	'block-meta'          => array(
 		'label'    => _x( 'Block Meta', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/block-meta.svg',
+		'public'   => true,
 	),
 	'block-table'         => array(
 		'label'    => _x( 'Block Table', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/block-table.svg',
+		'public'   => true,
 	),
 	'calendar'            => array(
 		'label'    => _x( 'Calendar', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/calendar.svg',
+		'public'   => true,
 	),
 	'capture-photo'       => array(
 		'label'    => _x( 'Capture Photo', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/capture-photo.svg',
+		'public'   => true,
 	),
 	'capture-video'       => array(
 		'label'    => _x( 'Capture Video', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/capture-video.svg',
+		'public'   => true,
 	),
 	'cart'                => array(
 		'label'    => _x( 'Cart', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/cart.svg',
+		'public'   => true,
 	),
 	'category'            => array(
 		'label'    => _x( 'Category', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/category.svg',
+		'public'   => true,
 	),
 	'caution'             => array(
 		'label'    => _x( 'Caution', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/caution.svg',
+		'public'   => true,
 	),
 	'chart-bar'           => array(
 		'label'    => _x( 'Chart Bar', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chart-bar.svg',
+		'public'   => true,
 	),
 	'check'               => array(
 		'label'    => _x( 'Check', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/check.svg',
+		'public'   => true,
 	),
 	'chevron-down'        => array(
 		'label'    => _x( 'Chevron Down', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-down.svg',
+		'public'   => true,
 	),
 	'chevron-down-small'  => array(
 		'label'    => _x( 'Chevron Down Small', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-down-small.svg',
+		'public'   => true,
 	),
 	'chevron-left'        => array(
 		'label'    => _x( 'Chevron Left', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-left.svg',
+		'public'   => true,
 	),
 	'chevron-left-small'  => array(
 		'label'    => _x( 'Chevron Left Small', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-left-small.svg',
+		'public'   => true,
 	),
 	'chevron-right'       => array(
 		'label'    => _x( 'Chevron Right', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-right.svg',
+		'public'   => true,
 	),
 	'chevron-right-small' => array(
 		'label'    => _x( 'Chevron Right Small', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-right-small.svg',
+		'public'   => true,
 	),
 	'chevron-up'          => array(
 		'label'    => _x( 'Chevron Up', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-up.svg',
+		'public'   => true,
 	),
 	'chevron-up-down'     => array(
 		'label'    => _x( 'Chevron Up Down', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-up-down.svg',
+		'public'   => true,
 	),
 	'chevron-up-small'    => array(
 		'label'    => _x( 'Chevron Up Small', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/chevron-up-small.svg',
+		'public'   => true,
 	),
 	'comment'             => array(
 		'label'    => _x( 'Comment', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/comment.svg',
+		'public'   => true,
 	),
 	'cover'               => array(
 		'label'    => _x( 'Cover', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/cover.svg',
+		'public'   => true,
 	),
 	'create'              => array(
 		'label'    => _x( 'Create', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/create.svg',
+		'public'   => true,
 	),
 	'desktop'             => array(
 		'label'    => _x( 'Desktop', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/desktop.svg',
+		'public'   => true,
 	),
 	'download'            => array(
 		'label'    => _x( 'Download', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/download.svg',
+		'public'   => true,
 	),
 	'drawer-left'         => array(
 		'label'    => _x( 'Drawer Left', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/drawer-left.svg',
+		'public'   => true,
 	),
 	'drawer-right'        => array(
 		'label'    => _x( 'Drawer Right', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/drawer-right.svg',
+		'public'   => true,
 	),
 	'envelope'            => array(
 		'label'    => _x( 'Envelope', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/envelope.svg',
+		'public'   => true,
 	),
 	'error'               => array(
 		'label'    => _x( 'Error', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/error.svg',
+		'public'   => true,
 	),
 	'external'            => array(
 		'label'    => _x( 'External', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/external.svg',
+		'public'   => true,
 	),
 	'file'                => array(
 		'label'    => _x( 'File', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/file.svg',
+		'public'   => true,
 	),
 	'gallery'             => array(
 		'label'    => _x( 'Gallery', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/gallery.svg',
+		'public'   => true,
 	),
 	'group'               => array(
 		'label'    => _x( 'Group', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/group.svg',
+		'public'   => true,
 	),
 	'heading'             => array(
 		'label'    => _x( 'Heading', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/heading.svg',
+		'public'   => true,
 	),
 	'help'                => array(
 		'label'    => _x( 'Help', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/help.svg',
+		'public'   => true,
 	),
 	'home'                => array(
 		'label'    => _x( 'Home', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/home.svg',
+		'public'   => true,
 	),
 	'image'               => array(
 		'label'    => _x( 'Image', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/image.svg',
+		'public'   => true,
 	),
 	'info'                => array(
 		'label'    => _x( 'Info', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/info.svg',
+		'public'   => true,
 	),
 	'key'                 => array(
 		'label'    => _x( 'Key', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/key.svg',
+		'public'   => true,
 	),
 	'language'            => array(
 		'label'    => _x( 'Language', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/language.svg',
+		'public'   => true,
 	),
 	'map-marker'          => array(
 		'label'    => _x( 'Map Marker', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/map-marker.svg',
+		'public'   => true,
 	),
 	'menu'                => array(
 		'label'    => _x( 'Menu', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/menu.svg',
+		'public'   => true,
 	),
 	'mobile'              => array(
 		'label'    => _x( 'Mobile', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/mobile.svg',
+		'public'   => true,
 	),
 	'more-horizontal'     => array(
 		'label'    => _x( 'More Horizontal', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/more-horizontal.svg',
+		'public'   => true,
 	),
 	'more-vertical'       => array(
 		'label'    => _x( 'More Vertical', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/more-vertical.svg',
+		'public'   => true,
 	),
 	'next'                => array(
 		'label'    => _x( 'Next', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/next.svg',
+		'public'   => true,
 	),
 	'paragraph'           => array(
 		'label'    => _x( 'Paragraph', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/paragraph.svg',
+		'public'   => true,
 	),
 	'payment'             => array(
 		'label'    => _x( 'Payment', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/payment.svg',
+		'public'   => true,
 	),
 	'pencil'              => array(
 		'label'    => _x( 'Pencil', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/pencil.svg',
+		'public'   => true,
 	),
 	'people'              => array(
 		'label'    => _x( 'People', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/people.svg',
+		'public'   => true,
 	),
 	'plus'                => array(
 		'label'    => _x( 'Plus', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/plus.svg',
+		'public'   => true,
 	),
 	'plus-circle'         => array(
 		'label'    => _x( 'Plus Circle', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/plus-circle.svg',
+		'public'   => true,
 	),
 	'previous'            => array(
 		'label'    => _x( 'Previous', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/previous.svg',
+		'public'   => true,
 	),
 	'published'           => array(
 		'label'    => _x( 'Published', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/published.svg',
+		'public'   => true,
 	),
 	'quote'               => array(
 		'label'    => _x( 'Quote', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/quote.svg',
+		'public'   => true,
 	),
 	'receipt'             => array(
 		'label'    => _x( 'Receipt', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/receipt.svg',
+		'public'   => true,
 	),
 	'rss'                 => array(
 		'label'    => _x( 'RSS', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/rss.svg',
+		'public'   => true,
 	),
 	'scheduled'           => array(
 		'label'    => _x( 'Scheduled', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/scheduled.svg',
+		'public'   => true,
 	),
 	'search'              => array(
 		'label'    => _x( 'Search', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/search.svg',
+		'public'   => true,
 	),
 	'settings'            => array(
 		'label'    => _x( 'Settings', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/settings.svg',
+		'public'   => true,
 	),
 	'shadow'              => array(
 		'label'    => _x( 'Shadow', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/shadow.svg',
+		'public'   => true,
 	),
 	'share'               => array(
 		'label'    => _x( 'Share', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/share.svg',
+		'public'   => true,
 	),
 	'shield'              => array(
 		'label'    => _x( 'Shield', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/shield.svg',
+		'public'   => true,
 	),
 	'shuffle'             => array(
 		'label'    => _x( 'Shuffle', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/shuffle.svg',
+		'public'   => true,
 	),
 	'star-empty'          => array(
 		'label'    => _x( 'Star Empty', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/star-empty.svg',
+		'public'   => true,
 	),
 	'star-filled'         => array(
 		'label'    => _x( 'Star Filled', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/star-filled.svg',
+		'public'   => true,
 	),
 	'star-half'           => array(
 		'label'    => _x( 'Star Half', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/star-half.svg',
+		'public'   => true,
 	),
 	'store'               => array(
 		'label'    => _x( 'Store', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/store.svg',
+		'public'   => true,
 	),
 	'styles'              => array(
 		'label'    => _x( 'Styles', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/styles.svg',
+		'public'   => true,
 	),
 	'symbol'              => array(
 		'label'    => _x( 'Symbol', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/symbol.svg',
+		'public'   => true,
 	),
 	'symbol-filled'       => array(
 		'label'    => _x( 'Symbol Filled', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/symbol-filled.svg',
+		'public'   => true,
 	),
 	'table'               => array(
 		'label'    => _x( 'Table', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/table.svg',
+		'public'   => true,
 	),
 	'tablet'              => array(
 		'label'    => _x( 'Tablet', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/tablet.svg',
+		'public'   => true,
 	),
 	'tag'                 => array(
 		'label'    => _x( 'Tag', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/tag.svg',
+		'public'   => true,
 	),
 	'tip'                 => array(
 		'label'    => _x( 'Tip', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/tip.svg',
+		'public'   => true,
 	),
 	'upload'              => array(
 		'label'    => _x( 'Upload', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/upload.svg',
+		'public'   => true,
 	),
 	'verse'               => array(
 		'label'    => _x( 'Verse', 'icon label', 'gutenberg' ),
 		'filePath' => 'library/verse.svg',
+		'public'   => true,
+	),
+	'wordpress'           => array(
+		'label'    => _x( 'WordPress', 'icon label', 'gutenberg' ),
+		'filePath' => 'library/wordpress.svg',
+		'public'   => false,
 	),
 );

Comment on lines +118 to +119
$scheme = is_admin() ? (string) get_user_option( 'admin_color' ) : 'fresh';
$colors = $schemes[ $scheme ] ?? $schemes['fresh'];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of https://core.trac.wordpress.org/changeset/61644 (WP 7.0), default is 'modern'

* Opens an output buffer just before the admin menu is rendered.
*/
function gutenberg_admin_wp_icons_menu_buffer_start() {
if ( gutenberg_admin_wp_icons_enabled() ) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and other identical checks can be removed as long as the loading of the whole file is safely guarded by a check that the experiment is active.

$map = gutenberg_admin_wp_icons_menu_map();

$html = preg_replace_callback(
'#<div class=([\'"])wp-menu-image dashicons-before dashicons-([a-z0-9-]+)\1[^>]*>.*?</div>#s',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar enough to really judge the solidity of this choice, but simple pattern matching in a place like filterable admin HTML gives me pause. :)

For instance, can we guarantee that exact substring of classes, or could third parties reorder or intersperse it?

Seems like another place to bring in WP_HTML_Tag_Processor, unless it proves too impractical.

@t-hamano

t-hamano commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

To be honest, I question whether this feature should be treated as an experimental feature in Gutenberg. This is because it requires irregular approaches such as direct access to the directory without going through the icon registry, replacement using regular expressions, and a lot of CSS.

We should consider adding an option to the icon registry as soon as possible to distinguish between public and private icons.

Related discussions:

Before that, it would be better to merge #79338 first. This is because this PR includes logic changes on how to synchronize icons to the core. cc @desrosj

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants