Custom icons in the WordPress block editor
Tags: wordpress, php, css
Published: 2021-03-27 01:43:54

It's been a while since I've done some WordPress work, so this was a nice change of pace. I read on the [WP Tavern](https://wptavern.com/insert-font-awesome-icons-into-the-block-editor-via-the-jvm-gutenberg-rich-text-icons-plugin) about a new plugin called [JVM Gutenberg Rich Text Icons](https://wordpress.org/plugins/jvm-rich-text-icons/), which allows icons to be used in rich text fields (e.g. paragraph and headings, etc).

I'm a big fan of [Nucleo](https://nucleoapp.com/) icons, some of which I already use in my WordPress theme [Garrick](https://github.com/peiche/garrick), but I've thought for a while now that it would be nice to have access to more icons as inline elements -- not just outside the editor or as block-level elements. This plugin fills that gap nicely, but it comes with Font Awesome 4 out of the box. Fortunately, it also provides some filters that allow for overriding this default functionality.

## Creating a Nucleo icon font

I'm getting ahead of myself, a little bit. Before I can override Font Awesome, I need a replacement icon font and stylesheet. Because Nucleo is a paid icon library and contains over 30,000 icons, it doesn't have a ready-made icon font the way Font Awesome 4 does. As a result, I followed Nucleo's [directions](https://nucleoapp.com/docs/exporting-icons#export-font) to create my own. As of this writing, the font I exported contains about 2,700 line icons. (The two main styles to choose from are solid and line, since full-color and duotone icons aren't possible with an icon font.)

## Using the icon font

The resulting exported fonts and stylesheet can be used directly in my WordPress child theme. Since I have a webpack (Laravel Mix, actually) build process, I put the files in these locations:

```
/resources/fonts/
/resources/scss/nucleo.scss
```

My build process copies the fonts and compiles the styles here, respectively:

```
/dist/fonts/
/dist/css/nucleo.css
```

Since the stylesheet already references the font files, I just need to override the WordPress plugin's stylesheet with its provided filter:

``` php
add_filter( 'jvm_richtext_icons_css_file', function( $cssfile ) {
  $cssfile = get_stylesheet_directory_uri() . '/dist/css/nucleo.css';
  return $cssfile;
} );
```

This stylesheet will get loaded in two places: the block editor and the rendered website.

## Selectable icons in the block editor

The stylesheet, however, is only half the puzzle. There still needs to be a way for the user to know what icons are available to select in the block editor. To solve this, a separate file is needed, which lists the class names. The plugin's default file is in `json` format, which looks like this (albeit truncated here for readability):

``` json
[
  "fa fa-address-book",
  "fa fa-address-card",
  "fa fa-adjust"
]
```

To be perfectly blunt, there is no easy or direct way to recreate this list for my Nucleo icons. The app does have a JSON export option, but it exports the SVG content of each icon, not just the names:

``` json
{
  "icons": [
    {
      "name": "piano",
      "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">...</svg>",
      "set_id": 1
    }
  ]
}
```

The icon font export contains an unformatted unicode list of all the icons, which is actually pretty close to what I want (again, edited for brevity):

``` json
{ "nucleo-icon-digital-key": 59906, "nucleo-icon-line-chart": 59907 }
```

Instead of an object with key-value paired properties, I just need a list of class named. To get this, I had to restructure the file a bit. I don't know of a way to completely automate this, but the process isn't so complicated that it can't be duplicated if I decide to add more icons.

First, I opened the file in Visual Studio Code and formatted the file (right-click > Format Document, or Shift+Alt+F):

``` json
{
  "nucleo-icon-digital-key": 59906,
  "nucleo-icon-line-chart": 59907
}
```

I added a comma to the last item, then selected all the lines inside the curly brackets. From the Selection menu, I clicked Add Cursors to Line Ends (Shift+Alt+I) and deleted the comma, unicode value, and colon:

``` json
{
  "nucleo-icon-digital-key"
  "nucleo-icon-line-chart"
}
```

While the multiple cursor mode was still active, I added a comma which added it to all the lines. Then I removed the comma from the last line and changed the curly brackets to square. I saved this file alongside my other pre-build files:

```
/resources/json/nucleo.json
```

When I run my build process, it's placed in the directory I want to reference from the filter.

```
/dist/json/nucleo.json
```

## Just JSON file things

Now here's where things got hairy. According to the documentation, I should have been able to use the following filter to override the plugin's `json` file:

``` php

add_filter( 'jvm_richtext_icons_iconset_file', function ( $file ) {
    $file = get_stylesheet_directory() . '/dist/json/nucleo.json';
    return $file;
} );
```

But this never worked. After some digging, I found this logic in the plugin:

``` php
$iconFile = apply_filters('jvm_richtext_icons_iconset_file', $iconFile);

$icons = [];
if (file_exists($iconFile)) {
  $iconData = file_get_contents($iconFile);
  $icons = json_decode($iconData);

  $icons = apply_filters('jvm_richtext_icons_iconset', $icons);            
}
```

For whatever reason, the `file_exists()` function call was returning false, even though I confirmed that my new `json` file was being loaded successfully. I did notice, however, that there was another filter where I could override things. So I updated my filter logic to use the `jvm_richtext_icons_iconset` filter instead:

``` php
add_filter( 'jvm_richtext_icons_iconset', function( $icons ) {
  $file = get_stylesheet_directory_uri() . '/dist/json/nucleo.json';
  $icons = [];
  $iconData = file_get_contents( $file );
  $icons = json_decode( $iconData );
  return $icons;
} );
```

I already knew my file existed, so instead of overriding the file, I overrode the collection being built using the file. And with that, I have successfully replaced Font Awesome with a custom Nucleo icon library for use within the WordPress block editor.

----

I do want to mention that in my WordPress theme, I have implemented icons by using individual SVGs instead of by icon font. By doing this, I avoid a couple problems which this plugin cannot in its current implementation address. Firstly, the entire icon library is loaded (via its fonts and stylesheet) with every page view. Secondly, there is a flash of the unsupported character in place of each icon until the fonts and stylesheet are loaded and parsed.

It is my hope that the plugin author will eventually address this and add support for loading SVGs in place of icon fonts. As it is, this plugin is the only one of its kind which allows for inline icons within the WordPress block editor.