Change an li class name in a wordpress custom menu walker

Troubleshooting

Enabling the WP_DEBUG constant in wp-config.php exposed the following errors:

– Function Signatures

If Strict Standards is enabled, you’ll see an error detailing incompatible method signatures. Though not absolutely necessary, I like to eliminate as many errors as possible. To correct this, the new Walker_Nav_Menu class’s start_el() and start_lvl() methods’ declarations need to match those of Walker_Nav_Menu class itself such that the new class can function as a drop-in replacement for the Walker_Nav_Menu class without throwing all sorts of parameter/argument related errors.

This:

class Wpse_145991_Walker_Nav_Menu extends Walker_Nav_Menu {
    function start_lvl( &$output, $depth ) {
        //...
    }

    public function start_el( &$output, $item, $depth, $args ) {
        //...
    }
}

should become this:

class Wpse_145991_Walker_Nav_Menu extends Walker_Nav_Menu {
    function start_lvl( &$output, $depth = 0, $args = array() ) {
        //...
    }

    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        //...
    }
}

Notice: Undefined variable: indent

Near the bottom of the new class’s start_el() method you’ll see the line

$output .= $indent . '<li class="nav-main-item">';

But $indent is never defined within method. Walker classes tend to set an $indent variable for formatting HTML white-space to a count of $depth tab-characters. It’s not functionally relevant, but this can be corrected by defining $indent somewhere before the aforementioned line:

$indent = str_repeat("\t", $depth);

At this point, the implementation posted in the question should be producing a menu without throwing any errors or warnings.


Specifying Different Classes for Different Depths

Theory

When a Walker_Nav_Menu is walk()ing, both the start_el() and start_lvl() methods are passed a $depth argument that can be thought of as representative of the number of “levels,” “submenus,” or <ul> elements between the current item and the data root (i.e. the top-level <ul> element.

Walker_Nav_Menu $depth Parameter

Implementation

By conditionally branching based on this $depth, you can assign different classes for different elements & levels. For instance, using a switch would allow you to fine-tune classes for each item and level:

class Wpse_145991_Walker_Nav_Menu extends Walker_Nav_Menu {
    function start_lvl( &$output, $depth = 0, $args = array() ) {
        $indent = str_repeat( "\t", $depth );

        // Select a CSS class for this `<ul>` based on $depth
        switch( $depth ) {
            case 0:
                // Top-level submenus get the 'nav-main-sub-list' class
                $class="nav-main-sub-list";
                break;
            case 1:
            case 2:
            case 3:
                // Submenus nested 1-3 levels deep get the 'nav-other-sub-list' class
                $class="nav-other-sub-list";
                break;
            default:
                // All other submenu `<ul>`s receive no class
                break;
        }

        // Only print out the 'class' attribute if a class has been assigned
        if( isset( $class ) )
            $output .= "\n$indent<ul class=\"$class\">\n";
        else
            $output .= "\n$indent<ul>\n";
    }

    function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
        $indent = str_repeat("\t", $depth);
        $attributes="";

        ! empty ( $item->attr_title )
        // Avoid redundant titles
        and $item->attr_title !== $item->title
        and $attributes .= ' title="' . esc_attr( $item->attr_title ) .'"';

        ! empty ( $item->url )
        and $attributes .= ' href="' . esc_attr( $item->url ) .'"';

        $attributes  = trim( $attributes );
        $title       = apply_filters( 'the_title', $item->title, $item->ID );
        $item_output = "$args->before<a $attributes>$args->link_before$title</a>"
                       . "$args->link_after$args->after";

        // Select a CSS class for this `<li>` based on $depth
        switch( $depth ) {
            case 0:
                // Top-level `<li>`s get the 'nav-main-item' class
                $class="nav-main-item";
                break;
            default:
                // All other `<li>`s receive no class
                break;
        }

        // Only print out the 'class' attribute if a class has been assigned
        if( isset( $class ) )
            $output .= $indent . '<li class="'. $class . '">';
        else
            $output .= $indent '<li>';

        $output .= apply_filters(
                'walker_nav_menu_start_el',
                $item_output,
                $item,
                $depth,
                $args
            );
    }
}