Add Nav Menu Icons Without a Plugin

Custom menu item icons

Add menu icons to your WordPress nav menus without using a plugin. This tutorial is really easy to customise, and it works with any version of Font Awesome… or any other icon pack you want to use.

There are lots of plugins that make it easy to add icons to WordPress menus, but they usually come bundled with a large icon set, which means more assets on your page, and that means a slower page-load. Because most sites already have access to Font Awesome anyway, just tap into that and add some tiny <i class="fa ..."></i> snippets into your menu items.

infoIf your site doesn’t already have Font Awesome on it, learn how to self-host Font Awesome yourself.
importantMake sure you’re using a custom child theme so you can edit functions.php.

How It Works

We’re going to keep things simple and just use CSS Classes to control which icons go where. When you edit a menu in WordPress, go to Screen Options in the top right and make sure that CSS Classes is enabled. When that’s enabled, you can add custom CSS Classes on a per menu item basis.

Show CSS classes in WordPress menu editor
Enable CSS Classes
WordPress custom menu icon CSS class
Custom menu item CSS Class

When we detect the custom “demo-icon-1” CSS Class we’ll inject a small Font Awesome Icon HTML snippet.

tipUpdated 21st Feb 2022: We’ve added a new class that we can detect called no-label, so we can render menu items that only contain an icon, like this:

If you do specify the no-label class like this, it’s a good idea to set the “Title Attribute” too, so the menu item still has some context – good for SEO.

Menu item with an icon but no label
A menu item with an icon and no label

Create the Source File

In your custom child theme, create a new file called nav-menu-icons.php and paste the following into it:

<?php

/**
 * WP Tutorials Nav Menu Icons
 *
 * https://wp-tutorials.tech/refine-wordpress/add-nav-menu-icons-without-a-plugin/
 *
 */

// Block direct access.
defined('WPINC') || die();

function custom_wp_nav_menu_objects($items) {
	if (is_array($items)) {
		// Loop through each of the menu items.
		foreach ($items as $item) {
			// $item->classes should always be an array, but it's good practice
			// to check it, so we don't accidentally crash the site if another
			// filter handler contains bad code.
			if (is_array($item->classes)) {
				$icon_html = null;

				// If the menu item has a CSS class called 'no-label' then
				// squash the label.
				$menu_label = $item->title;
				if (in_array('no-label', $item->classes)) {
					$menu_label = '';
				}

				foreach ($item->classes as $class) {
					switch ($class) {

					case 'demo-icon-1':
						$icon_html = '<i class="fas fa-star"></i>';
						break;

					case 'demo-icon-2':
						$icon_html = '<i class="fas fa-microphone"></i>';
						break;

					case 'demo-icon-3':
						$icon_html = '<i class="fas fa-bacon"></i>';
						break;

					case 'demo-icon-4':
						$icon_html = '<i class="fas fa-hippo"></i>';
						break;

					case 'email-icon':
						$icon_html = '<i class="fas fa-envelope"></i>';
						break;

					case 'image-icon':
						$icon_html = '<i class="fas fa-image"></i>';
						break;

					case 'phone-icon':
						$icon_html = '<i class="fas fa-phone"></i>';
						break;

					default:
						break;
					}

					if (!empty($icon_html)) {
						$item->title = $icon_html . $menu_label;

						// Break out of the nearest foreach loop, because we've
						// already found this menu item's icon.
						break;
					}
				}
			}
		}
	}

	return $items;
}
add_filter('wp_nav_menu_objects', 'custom_wp_nav_menu_objects');

The code should be easy to follow. Here’s the logic…

  • Every time WordPress builds a set of nav_menu_objects, hook the wp_nav_menu_objects filter
  • For each menu item…
    • For each of the menu item’s CSS Classes…
      • If the class is “demo-icon-1” then…
        • Set the icon HTML to the Font Awesome “star”
      • If the class is “demo-icon-2” then…
        • Set the icon HTML to the Font Awesome “microphone”
      • If we’ve set some icon HTML then…
        • Set the menu item’s text so it starts with the icon HTML
        • Break out of the for…loop and move on to the next menu item
  • Return the (possibly modified) array of menu items

You can add more case conditions for your own custom classes in the switch{ case... case... } statement. Now all we meed to do is import the code into the child theme. Open your child theme’s function.php file and add the following:

// WP Tutorials Nav Menu Icons.
require_once dirname(__FILE__) . '/nav-menu-icons.php';

That should do it. Now we can add some styles to spruce it up a bit.

Add Some Styles

Add the following to your child theme’s style.css, or paste it into the Customiser’s “Advanced CSS” editor:

/**
 * WPT Menu icons.
 */
.menu-item:not(.no-label)>.menu-link>i {
	margin-right: 0.5em;
}

.menu-item>.menu-link .fa-star {
	color: yellow;
	text-shadow:
		-1px -1px 0 #000,
		1px -1px 0 #000,
		-1px 1px 0 #000,
		1px 1px 0 #000;
}

/* More per-icon styles, if you want them... */

Wrapping Up

That’s it – a single function you can use in any WordPress project. We don’t need a bulky plugin, just to add a handful of icons to a site. You’re also not tied to a single icon library. You can inject any HTML you like, on a per-item basis. Elegant customisable menu icons for all your sites 😎👍

Like This Tutorial?

Let us know

WordPress plugins for developers

Leave a comment