Icon Fonts in WordPress Menus

A new theme I’m working on for city government has an interesting navigation feature. On the home page there’s an (optional) prominent menu that links to important sections of the website.

For each menu item, the user can select a font icon to use (from FontAwesome) by adding a css class. Here’s what the menu looks like with icons:

icon-menu

In order to set the class, that option needs to be enabled from the screen options at the top of the screen:

css-classes

From a development side, the menu needs to be registered:

register_nav_menus( array(
	'icon' => __( 'Icon Menu', 'textdomain' ),
) );

And displayed somewhere in the theme:

<?php
wp_nav_menu( array(
	'theme_location' => 'icon',
	'depth' => '1',
) ); ?>

The icon font needs to be registered:

function prefix_fonts() {
	// Add Genericons font, used in the main stylesheet.
	wp_enqueue_style( 'fontawesome', get_template_directory_uri() . '/fonts/font-awesome/font-awesome.css', array(), '4.0.3' );
}
add_action( 'wp_enqueue_scripts', 'prefix_fonts' );

The only issue is the WordPress menu applies the custom class to the li instead of the actual link. This might be fine if you want the icon to appear before the menu text, but not if you want the icon to actually be clickable.

Icon font css generally works like this:

[class^="icon-"]:before, [class*=" icon-"]:before, .icon-font {
  font-family: "FontAwesome";
  display: inline-block;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.icon-volume-up:before {
  content: "\f028";
}

To make the icon appear wrapped in the a tag, you will need to hide the icon :before the li, and adjust the specificity of the css:

.menu-icon li:before {
  content: none;
}
.menu-icon li[class^="icon-"] a:before, .menu-icon li[class*=" icon-"]:before{
  font-family: "FontAwesome";
  display: inline-block;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.menu-icon .icon-volume-up a:before {
  content: "\f028";
}

This is probably the best strategy in most situations. But I really didn’t really want to prefix 400 icon rules in the css, which lead me into the weeds of filtering menu markup…

(P.S. If you’re really interested in icon fonts for social menus, you should check out this tutorial by Justin Tadlock)

Into the Weeds

Ideally the custom css class for the icon would be applied directly to the a tag instead of it’s parent li. The following code works, but it does break the default behavior of WordPress which I’m not sure is the best idea (feel free to voice an opinion in the comments).

First, we’ll need remove the custom class from the li. This can be done using the nav_menu_css_class filter. Since my icon font classes are prefixed “icon-“, I can easily find and unset them from the $classes array.

/**
 * Removes any custom icon class applied to a menu item.
 *
 * @param array $classes Classes applied to menu item.
 * @return array
 */
function prefix_menu_css_class( $classes ) {

	foreach( $classes as $key => $val ) {
		if ( 'icon-' == substr( $val, 0, 5 ) ) {
			unset( $classes[$key] );
		}
	}
	return $classes;
}

add_filter( 'nav_menu_css_class', 'prefix_menu_css_class' );

Now we’ll need to apply that custom class to the a tag. We use the nav_menu_link_attributes filter for that:

/**
 * Filter the HTML attributes applied to a menu item's <a>.
 * If a custom icon class was applied to a menu item, it will
 * be placed on the a link rather than the li.
 *
 * @param array $atts {
 *     The HTML attributes applied to the menu item's <a>, empty strings are ignored.
 *
 *     @type string $title  The title attribute.
 *     @type string $target The target attribute.
 *     @type string $rel    The rel attribute.
 *     @type string $href   The href attribute.
 * }
 * @param object $item The current menu item.
 * @param array  $args An array of arguments. @see wp_nav_menu()
 */
function prefix_nav_menu_link_attributes( $atts, $item, $args ) {

        if ( 'icon' == $args->theme_location ) :

        // Default icon class
	$class = 'icon-info-circle';
	if ( $item->classes ) {
		foreach( $item->classes as $key => $val ) {
			if ( 'icon-' == substr( $val, 0, 5 ) ) {
				$class = $val;
			}
		}
	}
	if ( $class ) {
		$atts['class'] = $class;
	}

        endif;

	return $atts;

}

add_filter( 'nav_menu_link_attributes', 'prefix_nav_menu_link_attributes', 3, 10 );

Again, I’m not quite sure this is the best technique. But I’m sure someone will be searching for the nav_menu_link_attributes filter and find this useful.

If anyone has additional ideas or thoughts on how to make this work better, let me know. The source code for this theme project is on GitHub.

About Devin

I'm a WordPress developer based in Austin, Texas. I run a little theme shop called DevPress and work for a startup called Nano. Find me on twitter @devinsays.

Leave a Reply