Add an icon for your custom field type in Drupal's Field UI

Image
Someone with a lot of character doing a crossword puzzle

Drupal 10.2 introduced a revamped Field UI that most people would see as a very nice enhancement for site builders. Look at those icons for the different field types.

Screenshot featuring file upload, boolean, and link field icons

How does that all work?

More to the point, how can I get my Crossword field to have a cool icon? Because the default icon doesn't do justice to my favorite contribution to Drupal. Looks pretty drab if you ask me.

Crossword field uses the default icon

Luckily, I was able to give the Crossword field the flashy icon it deserved. This post takes you through the required steps in case you want to make the same improvement to your custom field type.

How core fields get their icons

I often find that the best way to learn how to do something in Drupal is to dig into the core codebase and see how it happens there. When it comes to the the new icons in Field UI, I modeled my work off of what I saw in the file and link modules, which use slightly different approaches.

The common elements of each approach are:

  1. An SVG to use as the icon where the stokes and fill use color #55565b. (See the ones in core)
  2. A very simple stylesheet that applies that SVG as a background image using a selector based on the field type
  3. A library that includes your new stylesheet
  4. Some way to tell Field UI about the library

The two modules I looked at differed in the details of step 4.

The SVG

The SVGs I saw in core are all 36x36 with fill and stroke of #55565b. I didn't take the time to determine what happens if I strayed from those norms. I just went along with it and created a simple SVG with those specs. I keyed my SVG in by hand in a code editor, but you could make yours however you want.

The Stylesheet

The stylesheet that makes the fancy icon for the link field type looks like this:

.field-icon-link {
  background-image: url("data:image/svg+xml,%3csvg height='38' viewBox='0 0 38 38' ... ");
}

That selector is easy enough to generalize from. And while core processes a pcss file to generate that background-image property based on the image url, I was able to generate the same by uploading my SVG to a cool Codepen I came across. That led me to this stylesheet for my project:

.field-icon-crossword {
  background-image: url("data:image/svg+xml,%3Csvg height='36' viewBox='0 0 36 36' ... ");
}

The Library

Imitating the library from link or file was straightforward and led to this new library in Crossword:

crossword.crossword-icon:
  css:
    theme:
      css/crossword.icon.theme.css: {}
  dependencies:
    - field_ui/drupal.field_ui.manage_fields

Publicizing the library

This is where link and file differ. Why they differ comes down to the category of the field type. Note that the link field type does not have a category while the file field is part of the file_upload category.

When there's no category, a hook is used to tell Field UI about the library with the icon, as in link.module.

 * Implements hook_field_type_category_info_alter().
 */
function link_field_type_category_info_alter(&$definitions) {
  // The `link` field type belongs in the `general` category, so the libraries
  // need to be attached using an alter hook.
  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'link/drupal.link-icon';
}

The file module, on the other hand, uses a yaml plugin (file.field_type_category.yml) to alert Field UI of the library, something it can do because the file field is part of the file_upload category.

file_upload:
  label: 'File upload'
  description: 'Field to upload any type of files.'
  weight: -15
  libraries:
    - file/drupal.file-icon

The hook approach was originally the only supported technique, with the yaml plugin system being added in a later iteration of the community's Field UI work. (The full API is described in a single CR.)

So which approach makes sense for the Crossword module? If we look at the v2.0.1 release we see that the Crossword field has a category but that the category is a translatable string. That's different from the file field type, where the category is a machine name. This is a result of the Crossword field type still using the older annotation while core fields have moved to php attributes. It turns out that when the category is set to a translatable string, it's as if there is no category. We would use the hook approach as in link.

/**
 * Implements hook_field_type_category_info_alter().
 */
function crossword_field_type_category_info_alter(&$definitions) {
  // The `crossword` field type belongs in the `general` category, so the libraries
  // need to be attached using an alter hook.
  $definitions[FieldTypeCategoryManagerInterface::FALLBACK_CATEGORY]['libraries'][] = 'crossword/crossword.crossword-icon';
}

For completeness, I should add that I opted to remove the category entirely. If I update it to the machine name reference then I don't get to add the cool icon; the field would not get its own tile. On the other hand, if I update the category to crossword, then Field UI expects there to be more than one field type in that category. It essentially forces me to select the Crossword field twice. Neither of those options is good, so I went with removing the category entirely.

Pay dirt

Now the Crossword field is as cool as it should be.

The crossword field has a cool icon now

We got issues

I copied off of core, but there are lots of other people to copy off of. Here are a few issues where contributed projects beautify the Field UI tiles for field types they provide:

In addition to supporting maintenance of the Crossword module, folks at Horizontal maintain a number of other modules that provide new field types, including Viewfield and Style Entity. Maybe you can jump in and help us come up with some cool icons for them!