Cleaner output for wp_nav_menu()


If you are looking for a way to have a clean navigation list without the default classes and ids then the simplest solution I found is to extend the Walker class with a modified version of the Walker_Nav_Menu class.

The code below is for my custom Walker which is actually the same code as the navigation Walker shipped with WordPress minus the echo of default CSS classes and ids. To make this work simply drop the code in your theme’s functions.php.

<?php
class MV_Cleaner_Walker_Nav_Menu extends Walker {
	var $tree_type = array( 'post_type', 'taxonomy', 'custom' );
	var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );
	function start_lvl(&$output, $depth) {
		$indent = str_repeat("\t", $depth);
		$output .= "\n$indent<ul class=\"sub-menu\">\n";
	}
	function end_lvl(&$output, $depth) {
		$indent = str_repeat("\t", $depth);
		$output .= "$indent</ul>\n";
	}
	function start_el(&$output, $item, $depth, $args) {
		global $wp_query;
		$indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';
		$class_names = $value = '';
		$classes = empty( $item->classes ) ? array() : (array) $item->classes;
		$classes = in_array( 'current-menu-item', $classes ) ? array( 'current-menu-item' ) : array();
		$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
		$class_names = strlen( trim( $class_names ) ) > 0 ? ' class="' . esc_attr( $class_names ) . '"' : '';
		$id = apply_filters( 'nav_menu_item_id', '', $item, $args );
		$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
		$output .= $indent . '<li' . $id . $value . $class_names .'>';
		$attributes  = ! empty( $item->attr_title ) ? ' title="'  . esc_attr( $item->attr_title ) .'"' : '';
		$attributes .= ! empty( $item->target )     ? ' target="' . esc_attr( $item->target     ) .'"' : '';
		$attributes .= ! empty( $item->xfn )        ? ' rel="'    . esc_attr( $item->xfn        ) .'"' : '';
		$attributes .= ! empty( $item->url )        ? ' href="'   . esc_attr( $item->url        ) .'"' : '';
		$item_output = $args->before;
		$item_output .= '<a'. $attributes .'>';
		$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
		$item_output .= '</a>';
		$item_output .= $args->after;
		$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
	}
	function end_el(&$output, $item, $depth) {
		$output .= "</li>\n";
	}
}
?>

Then make sure to specify the new custom Walker when calling wp_nav_menu().

<?php
 wp_nav_menu( array( 'walker' => new MV_Cleaner_Walker_Nav_Menu() ) );
?>

Example:

<ul id="menu-main" class="menu">
	<li><a href="http://www.mattvarone.com/about/">About</a></li>
	<li><a href="http://www.mattvarone.com/featured-content/">Featured</a></li>
	<li><a href="http://www.mattvarone.com/contact/">Contact</a></li>
	<li><a href="http://www.mattvarone.com/blog/">Blog</a></li>
</ul>

Reactions (7)

    • Hi Shaun,

      I brought back the nav_menu_css_class and nav_menu_item_id filters. You can use these optionally to add classes and id’s.

      For example:

      function mv_custom_menu_classes( $c )
      {
      	$c[] = 'test';
      	return $c;
      }
      add_filter( 'nav_menu_css_class', 'mv_custom_menu_classes' );
      

      Would output:

      <ul id="menu-main" class="menu">
      	<li class="test"><a href="#">Home</a></li>
      </ul>
      
  1. Thank you for this. It’s been a big help. However, when clicking on a menu item, wordpress no longer attaches the ‘current-menu-item’ class. Any word on how to restore that and keep the clean markup?

    Thanks.

    • Yes, my question is the same. How it’s possible to allow the ‘current-menu-item’ class?

  2. This is great, but I also remove sub-menus, how I utilize this and have sub-menus as well?

  3. Sorry for the delay guys. Class updated, now it adds ‘current-menu-item’.

  4. use:
    $current_url = (is_ssl()?’https://’:’http://’).$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
    $item_url = esc_attr( $item->url );
    if ( $item_url == $current_url ){
    $attributes .= ‘bla-bla-bla’;
    }