|

Adding Custom Icons to the Icon Block

Since its release, the Icon Block for WordPress has included a basic library of icons sourced from @wordpress/icons. The block also allows you to insert custom SVG icons ad hoc, but this can be tedious especially if you’re inserting the same icon over and over.

I always intended for users to be able to add their own icons to the block itself. Over 4,000 active installs later, enabling custom icon libraries is by far the most common feature request.

Well, I am pleased to announce that this is now possible as of version 1.2.0 and this article will show you how. ⚡️

Getting Started

The Icons Block is a static block built almost entirely with JavaScript, more specifically, with React. Therefore, icons need to be added using JavaScript but don’t worry, it’s quite straightforward.

I am going to assume that you want to register a custom icon library in a WordPress theme. You could also adapt the following steps for a custom plugin.

In the function.php file of your theme, add the following:

function example_register_custom_icons() {
	wp_enqueue_script(
		'example-register-custom-icons',
		get_theme_file_uri( '/assets/js/register-custom-icons.js' ),
		array( 'wp-i18n', 'wp-hooks', 'wp-dom' ),
		wp_get_theme()->get( 'Version' ),
		true // Very important, otherwise the filter is called too early.
	);
}
add_action( 'enqueue_block_editor_assets', 'example_register_custom_icons' );

This should be familiar to those that have enqueued scripts and/or styles before. A couple of things to note:

  • The path to the JavaScript file can be anything. I generally organize my asset files in their own folder but it’s up to you.
  • The array defines the WordPress dependencies that we’ll need.
  • The true at the end is very important. It tells WordPress to load the file in the footer rather than the header. This file needs to be loaded after the Icon Block.

Finally, create the blank register-custom-icons.js file. Name the file whatever you like, just make sure it’s correctly referenced in the enqueue function above.

Now we’re ready to add a custom icon library.

Adding Custom Icons

Let’s start with the complete example implementation and then we’ll walk through each section. In the register-custom-icons.js file, add the following:

// Add custom icons to the Icon Block.
wp.domReady( () => {

	const { __ } = wp.i18n;
	const { addFilter } = wp.hooks;

	function addCustomIcons( icons ) {

		const customIcons = [
			{
				isDefault: true,
				name: 'bookmark',
				title: __( 'Bookmark', 'icon-block' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M15.5,4.25 L9,4.25 C7.48121644,4.25 6.25,5.48121644 6.25,7 L6.25,19.5 L6.25678483,19.6039817 C6.33269168,20.17682 7.02861659,20.4632798 7.48809353,20.0694425 L12.25,15.988 L17.0119065,20.0694425 C17.4984115,20.4864467 18.25,20.1407649 18.25,19.5 L18.25,7 C18.25,5.48120782 17.0188049,4.25 15.5,4.25 Z M15.5,5.75 L15.6278083,5.75645347 C16.2581415,5.82046462 16.75,6.3527795 16.75,7 L16.75,17.869 L12.7380935,14.4305575 L12.63989,14.3592262 C12.3667749,14.1927863 12.0115663,14.2165634 11.7619065,14.4305575 L7.75,17.868 L7.75,7 C7.75,6.30964356 8.30964356,5.75 9,5.75 L15.5,5.75 Z"></path></svg>',
				categories: [ 'category-one', 'category-two' ],
			},
			{
				name: 'cloud',
				title: __( 'Cloud', 'icon-block' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M12.0297869,6.25477399 C9.69310799,6.35627347 7.76028359,8.06517251 7.33640731,10.3195752 L7.33,10.357 L7.17131855,10.3970995 C5.4791682,10.8693286 4.25,12.4228808 4.25,14.25 C4.25,16.4591229 6.04086573,18.25 8.25,18.25 L16.25,18.25 C18.4591136,18.25 20.25,16.4591136 20.25,14.25 L20.2449807,14.0481533 C20.1583164,12.3092839 18.9581728,10.8518379 17.3286997,10.3970993 L17.169,10.357 L17.1636012,10.3195631 C16.726443,7.99471518 14.6845932,6.25 12.25,6.25 L12.0297869,6.25477399 Z M12.25,7.75 C14.1089517,7.75 15.641164,9.20368247 15.7444701,11.050879 C15.7655051,11.4270003 16.0622764,11.7291586 16.4379585,11.7569554 C17.7370378,11.8530748 18.75,12.9390845 18.75,14.25 C18.75,15.6306864 17.6306864,16.75 16.25,16.75 L8.25,16.75 C6.86929499,16.75 5.75,15.6306979 5.75,14.25 C5.75,12.9390902 6.76297645,11.8530752 8.06206149,11.7569554 C8.43774872,11.7291582 8.7345221,11.4269924 8.7555506,11.0508656 C8.85882307,9.20368024 10.391028,7.75 12.25,7.75 Z"></path></svg>',
				categories: [ 'category-one', 'category-two' ],
			},
			{
				name: 'heart',
				title: __( 'Heart', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M6.08714334,6.32932979 C3.96268923,8.06801188 3.63859886,11.0218225 5.33411073,13.1117301 L5.40693404,13.189484 L11.735394,19.050284 C12.0229467,19.3165872 12.4670552,19.3165879 12.7546087,19.0502856 L19.0831087,13.1894856 L19.1559241,13.1117421 C20.8618901,11.009037 20.5476544,8.03670134 18.3950317,6.32296945 L18.2021622,6.17656493 C16.3839665,4.86189789 14.0726771,4.99410522 12.3732475,6.33543793 L12.244,6.441 L12.1137885,6.33327648 C10.4051427,4.9868485 8.07169216,4.85041921 6.27728692,6.18112693 L6.08714334,6.32932979 Z M11.6855857,7.98275475 C11.9837742,8.31667652 12.5062017,8.31668548 12.8044017,7.98277393 C14.0606994,6.57602557 16.0037503,6.33654686 17.4607748,7.49649577 L17.6164202,7.627428 C18.9226437,8.78850986 19.1174439,10.620049 18.1123793,12.0085048 L18.021,12.127 L12.245,17.477 L6.468,12.127 L6.49897378,12.1666936 C5.32705305,10.7221704 5.54794474,8.70892882 7.0371596,7.49013516 C8.45843106,6.32695371 10.4260915,6.57232977 11.6855857,7.98275475 Z"></path></svg>',
				categories: [ 'category-one' ],
				keywords: [ 'love' ],
			},
			{
				name: 'mail',
				title: __( 'Mail', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M17.5,5.25 C19.0188049,5.25 20.25,6.48120782 20.25,8 L20.25,8 L20.25,16.5 C20.25,18.0188136 19.0188136,19.25 17.5,19.25 L17.5,19.25 L7,19.25 C5.48120782,19.25 4.25,18.0188049 4.25,16.5 L4.25,16.5 L4.25,8 C4.25,6.48121644 5.48121644,5.25 7,5.25 L7,5.25 Z M18.7288492,7.76980088 L12.7469304,13.0617474 C12.4915617,13.2876505 12.1206612,13.3102408 11.8421862,13.1295183 L11.7530696,13.0617474 L5.77115144,7.76980412 C5.75726181,7.84441732 5.75,7.92136061 5.75,8 L5.75,8 L5.75,16.5 C5.75,17.1903743 6.30963146,17.75 7,17.75 L7,17.75 L17.5,17.75 C18.1903864,17.75 18.75,17.1903864 18.75,16.5 L18.75,16.5 L18.75,8 C18.75,7.92135923 18.7427384,7.84441487 18.7288492,7.76980088 Z M17.5,6.75 L7,6.75 C6.96190904,6.75 6.92421603,6.75170376 6.886993,6.75503923 L12.249,11.499 L17.612,6.755 L17.5,6.75 Z"></path></svg>',
				categories: [ 'category-one' ],
			},
			{
				name: 'search',
				title: __( 'Search', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M11.25,4.25 C15.116007,4.25 18.25,7.3839999 18.25,11.25 C18.25,12.9128461 17.670205,14.4402654 16.7017462,15.6411267 L20.0303301,18.9696699 C20.3232233,19.2625631 20.3232233,19.7374369 20.0303301,20.0303301 C19.7640635,20.2965966 19.3473998,20.3208027 19.0537883,20.1029482 L18.9696699,20.0303301 L15.6411267,16.7017462 C14.4402654,17.670205 12.9128461,18.25 11.25,18.25 C7.3839999,18.25 4.25,15.116007 4.25,11.25 C4.25,7.38400644 7.38400644,4.25 11.25,4.25 Z M11.25,5.75 C8.21243356,5.75 5.75,8.21243356 5.75,11.25 C5.75,14.2875792 8.21242628,16.75 11.25,16.75 C14.2875864,16.75 16.75,14.2875864 16.75,11.25 C16.75,8.21242628 14.2875792,5.75 11.25,5.75 Z"></path></svg>',
				categories: [ 'category-two' ],
			},
			{
				isDefault: true,
				name: 'user',
				title: __( 'User', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M11.800508,4.25 C9.59136901,4.25 7.800508,6.040861 7.800508,8.25 C7.800508,10.459139 9.59136901,12.25 11.800508,12.25 C14.009647,12.25 15.800508,10.459139 15.800508,8.25 C15.800508,6.040861 14.009647,4.25 11.800508,4.25 Z M11.800508,5.75 C13.1812199,5.75 14.300508,6.86928813 14.300508,8.25 C14.300508,9.63071187 13.1812199,10.75 11.800508,10.75 C10.4197961,10.75 9.300508,9.63071187 9.300508,8.25 C9.300508,6.86928813 10.4197961,5.75 11.800508,5.75 Z M11.800508,13.5 C8.04670918,13.5 5.68353852,14.9106243 4.49655562,17.1581489 C3.69148307,18.6824832 4.93973466,20.25 6.648008,20.25 L16.953008,20.25 C18.6612415,20.25 19.9094839,18.6825146 19.1045145,17.1581755 C17.917388,14.9105796 15.5542922,13.5 11.800508,13.5 Z M11.800508,15 C15.00061,15 16.8519251,16.1050874 17.7781285,17.8586756 C18.0005848,18.2799324 17.6262539,18.75 16.953008,18.75 L6.648008,18.75 C5.97474208,18.75 5.60043156,18.2799521 5.82293547,17.8586605 C6.74903109,16.1051199 8.60040067,15 11.800508,15 Z"></path></svg>',
				categories: [ 'category-two' ],
			},
		];

		const customIconCategories = [
			{
				name: 'category-one',
				title: __( 'Category One', 'example-theme' ),
			},
			{
				name: 'category-two',
				title: __( 'Category Two', 'example-theme' ),
			},
		];

		const customIconType = [
			{
				isDefault: true,
				type: 'example-icons',
				title: __( 'Example Icons', 'example-theme' ),
				icons: customIcons,
				categories: customIconCategories,
			},
		];

		const allIcons = [].concat( icons, customIconType );

		return allIcons;
	}

	addFilter(
		'iconBlock.icons',
		'example-theme/example-custom-icons',
		addCustomIcons
	);
} );

This code will add 6 new icons to the Icon Block. They will appear as their own icon type called “Example Icons”. We have also registered two icon categories.

Here are a few screenshots of what the finished product looks like:

Example Icons in the Icon Block quick inserter.
Example Icons in the Quick Inserter
Example Icons in the Icon Block inserter modal.
Example Icons in the Inserter modal

The Filter

Let’s simplify the code above. You’ll see that we are passing a callback function to the filter iconBlock.icons.

// Add custom icons to the Icon Block.
wp.domReady( () => {

	const { __ } = wp.i18n;
	const { addFilter } = wp.hooks;

	function addCustomIcons( icons ) {

		const customIconType = [ ... ];

		const allIcons = [].concat( icons, customIconType );

		return allIcons;
	}

	addFilter(
		'iconBlock.icons',
		'nick-diego/example-theme/example-custom-icons',
		addCustomIcons
	);
} );

The function and filter need to wrapped in wp.domReady(), which is one of the dependencies we included in the enqueue function. I have also simplified the other dependencies so they’re a bit easier to work with, but this is not required.

addFilter() is part of the Hooks API and is very similar to the PHP add_filter() function. The first parameter is the filter itself. The second is a unique namespace, which can be anything, but generally follows the format vendor/theme/function. The final parameter is the callback function.

The callback function addCustomIcons() accepts a single parameter icons that’s an array of all icons currently available in the Icon Block. By default, this would be the base icon library provided by @wordpress/icons.

Once an array of custom icons is defined in customIconType, which we’ll talk about next, I have concatenated the original icons array with the new array. You could also remove the WordPress icons from the plugin entirely and just use your custom icons. To do so, return customIconType instead of allIcons in the code above.

Icon Type

Custom icons are added to the Icon Block by defining an icon type. In the following example code, I have defined the type “Example Icons”. You can register multiple types.

const customIconType = [
	{
		isDefault: false,
		type: 'example-icons',
		title: __( 'Example Icons', 'example-theme' ),
		icons: customIcons,
		categories: customIconCategories,
	},
];

An icon type is defined by five parameters. All are required unless specified:

  • isDefault – Defaults to false. Determines if icons in this type should appear first in the Quick Inserter and Inserter Modal. (optional)
  • type – A slug to define the icon type.
  • title – A descriptive title for the icon type.
  • icons – An array of all custom icons in the icon type.
  • categories – An array of all icon categories in the icon type. (optional)

Icons

Next, we need to add the icons themselves.

const customIcons = [
	{ ... },
	{
		isDefault: false,
		name: 'heart',
		title: __( 'Heart', 'custom-icons' ),
		icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M6.08714334,6.32932979 C3.96268923,8.06801188 3.63859886,11.0218225 5.33411073,13.1117301 L5.40693404,13.189484 L11.735394,19.050284 C12.0229467,19.3165872 12.4670552,19.3165879 12.7546087,19.0502856 L19.0831087,13.1894856 L19.1559241,13.1117421 C20.8618901,11.009037 20.5476544,8.03670134 18.3950317,6.32296945 L18.2021622,6.17656493 C16.3839665,4.86189789 14.0726771,4.99410522 12.3732475,6.33543793 L12.244,6.441 L12.1137885,6.33327648 C10.4051427,4.9868485 8.07169216,4.85041921 6.27728692,6.18112693 L6.08714334,6.32932979 Z M11.6855857,7.98275475 C11.9837742,8.31667652 12.5062017,8.31668548 12.8044017,7.98277393 C14.0606994,6.57602557 16.0037503,6.33654686 17.4607748,7.49649577 L17.6164202,7.627428 C18.9226437,8.78850986 19.1174439,10.620049 18.1123793,12.0085048 L18.021,12.127 L12.245,17.477 L6.468,12.127 L6.49897378,12.1666936 C5.32705305,10.7221704 5.54794474,8.70892882 7.0371596,7.49013516 C8.45843106,6.32695371 10.4260915,6.57232977 11.6855857,7.98275475 Z"></path></svg>',
		categories: [ 'category-one' ],
		keywords: [ 'love' ],
		hasNoIconFill: false,
	},
	{ ... },
];

Icons in the customIcons array are defined by seven parameters. All are required unless specified:

  • isDefault – Defaults to false. Determines if the icon should be displayed first in the list of available icons. (optional)
  • name – A slug to define the icon.
  • title – A descriptive title for the icon.
  • icon – A string of the SVG code that makes up the icon.
  • categories – An array of category names that the icon belongs to. (optional)
  • keywords – An array of keywords that help define the icon. Keywords are used when the user is searching for icons. (optional)
  • hasNoIconFill – Defaults to false. Set to true for icons that use stroke attributes and/or should not have a color fill applied. (optional)

Categories

Finally, we need to define icon categories. Categories are optional, but they are a great way to organize your custom icons.

const customIconCategories = [
	{
		name: 'category-one',
		title: __( 'Category One', 'example-theme' ),
	},
	{
		name: 'category-two',
		title: __( 'Category Two', 'example-theme' ),
	},
];

Categories in the customIconCategories array are defined by two parameters. All are required:

  • name – A slug to define the icon category.
  • title – A descriptive title for the icon category.

That’s it, you can now add your own icon library to the Icon Block!

Closing Thoughts

There are a few tips I wanted to share regarding custom SVG icons, especially if you are designing them yourself.

  1. To take full advantage of the Icon Block and its internal controls, I recommended converting all SVGs to outlines if possible.
  2. Flatten your SVGs before exporting them from your design application. This will generally make the string smaller.
  3. After exporting from your design application, remove all unnecessary markup from the SVG code. This will make the string smaller and easier to work with.
  4. Unless you need SVGs to be a specific color, remove fill and other color values. Icon color can be controlled from within the block.
  5. If your SVG does need to include fill or other color values, make sure they are added to the SVG itself. The block will strip any <style> markup included with the SVG.

To follow Icon Block development, star the repository on GitHub or follow on Twitter. If you have any questions or a feature that you would like to see, let me know.

Latest Articles