Since WordPress make use of Walkers
you can extend the class Walker_Nav_Menu
with your own and pass the instance of it to the function wp_nav_menu
.
So your call will be:
<?php wp_nav_menu( array(
'menu' => 'Main Menu'
'walker' => new Custom_Nav_Walker()
) ); ?>
Then in a separated file create the class Custom_Nav_Walker
that will be your walker.
The class extends two methods of Walker_Nav_Menu
that are start_el
and end_el
. The code within them is pretty the same of the main class, except that we introduce a little tweak to wrap the nav items with a div
.
WordPress unfortunately mix up php and html code sometimes using string like this one $output .= $indent . '<li' . $id . $class_names . '>';
Within the Walker_Nav_Menu::start_el
you’ll find that line of code that is the opened tag for all of the list items ( the method is called everytime a new item need to be added to the list ).
If we change it with this one $output .= $indent . (0 === $depth ? '<div ' : '<li ' ) . $id . $class_names . '>';
we’ll use the div
tag for all of the items at the first level.
Then in the method end_el
we change this one $output .= "</li>{$n}";
with $output .= (0 === $depth ? "</div>{$n}" : "</li>{$n}");
so we can close the previously opened div
tag.
Unfortunately unless you deep into complicated regexp this is the best and rapid solution to achive your goal.
The problem with this solution is that you must extend the Walker_Nav_Menu
class and override the two methods above, meaning unnecessary code to maintain in case of WordPress change something.
Here the entire subclass used as walker for the menu.
class Custom_Nav_Walker extends Walker_Nav_Menu {
public function start_el( &$output, $item, $depth = 0, $args = [], $id = 0 ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t="";
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$indent = ( $depth ) ? str_repeat( $t, $depth ) : '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
/**
* Filters the arguments for a single nav menu item.
*
* @since 4.4.0
*
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param WP_Post $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
*/
$args = apply_filters( 'nav_menu_item_args', $args, $item, $depth );
/**
* Filters the CSS class(es) applied to a menu item's list item element.
*
* @since 3.0.0
* @since 4.1.0 The `$depth` parameter was added.
*
* @param array $classes The CSS classes that are applied to the menu item's `<li>` element.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args, $depth ) );
$class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
/**
* Filters the ID applied to a menu item's list item element.
*
* @since 3.0.1
* @since 4.1.0 The `$depth` parameter was added.
*
* @param string $menu_id The ID that is applied to the menu item's `<li>` element.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$id = apply_filters( 'nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args, $depth );
$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . (0 === $depth ? '<div ' : '<li ' ) . $id . $class_names . '>';
$atts = array();
$atts['title'] = ! empty( $item->attr_title ) ? $item->attr_title : '';
$atts['target'] = ! empty( $item->target ) ? $item->target : '';
$atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : '';
$atts['href'] = ! empty( $item->url ) ? $item->url : '';
/**
* Filters the HTML attributes applied to a menu item's anchor element.
*
* @since 3.6.0
* @since 4.1.0 The `$depth` parameter was added.
*
* @param array $atts {
* The HTML attributes applied to the menu item's `<a>` element, empty strings are ignored.
*
* @type string $title Title attribute.
* @type string $target Target attribute.
* @type string $rel The rel attribute.
* @type string $href The href attribute.
* }
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args, $depth );
$attributes="";
foreach ( $atts as $attr => $value ) {
if ( ! empty( $value ) ) {
$value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
$attributes .= ' ' . $attr . '="' . $value . '"';
}
}
/** This filter is documented in wp-includes/post-template.php */
$title = apply_filters( 'the_title', $item->title, $item->ID );
/**
* Filters a menu item's title.
*
* @since 4.4.0
*
* @param string $title The menu item's title.
* @param WP_Post $item The current menu item.
* @param stdClass $args An object of wp_nav_menu() arguments.
* @param int $depth Depth of menu item. Used for padding.
*/
$title = apply_filters( 'nav_menu_item_title', $title, $item, $args, $depth );
$item_output = $args->before;
$item_output .= '<a' . $attributes . '>';
$item_output .= $args->link_before . $title . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
/**
* Filters a menu item's starting output.
*
* The menu item's starting output only includes `$args->before`, the opening `<a>`,
* the menu item's title, the closing `</a>`, and `$args->after`. Currently, there is
* no filter for modifying the opening and closing `<li>` for a menu item.
*
* @since 3.0.0
*
* @param string $item_output The menu item's starting HTML output.
* @param WP_Post $item Menu item data object.
* @param int $depth Depth of menu item. Used for padding.
* @param stdClass $args An object of wp_nav_menu() arguments.
*/
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
public function end_el( &$output, $item, $depth = 0, $args = [] ) {
if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) {
$t="";
$n = '';
} else {
$t = "\t";
$n = "\n";
}
$output .= (0 === $depth ? "</div>{$n}" : "</li>{$n}");
}
}
I leave it the comments blocks from the original methods, but you get rid of them if you don’t want to have info about the filters.
The class Walker_Nav_Menu
is located into wp-includes/class-walker-nav-menu.php
.