PHPwordpressadvanced

WordPress Custom Menu Walker

Create custom navigation menu HTML structure with a custom walker class

#wordpress#menu#navigation#walker#theme-development
Share this snippet:

Code

php
1// Custom Walker Class for Bootstrap 5
2class Bootstrap_5_Walker_Nav_Menu extends Walker_Nav_Menu {
3
4 /**
5 * Start Level - Opening <ul>
6 */
7 function start_lvl(&$output, $depth = 0, $args = null) {
8 $indent = str_repeat("\t", $depth);
9 $classes = array('dropdown-menu');
10 $class_names = implode(' ', $classes);
11
12 $output .= "\n$indent<ul class=\"$class_names\">\n";
13 }
14
15 /**
16 * Start Element - Opening <li>
17 */
18 function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
19 $indent = ($depth) ? str_repeat("\t", $depth) : '';
20
21 $classes = empty($item->classes) ? array() : (array) $item->classes;
22 $classes[] = 'nav-item';
23 $classes[] = 'menu-item-' . $item->ID;
24
25 if ($args->walker->has_children) {
26 $classes[] = 'dropdown';
27 }
28
29 if (in_array('current-menu-item', $classes)) {
30 $classes[] = 'active';
31 }
32
33 $class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args));
34 $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';
35
36 $id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args);
37 $id = $id ? ' id="' . esc_attr($id) . '"' : '';
38
39 $output .= $indent . '<li' . $id . $class_names . '>';
40
41 // Link attributes
42 $atts = array();
43 $atts['title'] = !empty($item->attr_title) ? $item->attr_title : '';
44 $atts['target'] = !empty($item->target) ? $item->target : '';
45 $atts['rel'] = !empty($item->xfn) ? $item->xfn : '';
46 $atts['href'] = !empty($item->url) ? $item->url : '';
47 $atts['class'] = 'nav-link';
48
49 // Dropdown toggle
50 if ($args->walker->has_children) {
51 $atts['class'] .= ' dropdown-toggle';
52 $atts['data-bs-toggle'] = 'dropdown';
53 $atts['aria-expanded'] = 'false';
54 }
55
56 // Current item
57 if (in_array('current-menu-item', $item->classes)) {
58 $atts['class'] .= ' active';
59 $atts['aria-current'] = 'page';
60 }
61
62 $atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args);
63
64 $attributes = '';
65 foreach ($atts as $attr => $value) {
66 if (!empty($value)) {
67 $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
68 $attributes .= ' ' . $attr . '="' . $value . '"';
69 }
70 }
71
72 $item_output = $args->before;
73 $item_output .= '<a' . $attributes . '>';
74 $item_output .= $args->link_before . apply_filters('the_title', $item->title, $item->ID) . $args->link_after;
75 $item_output .= '</a>';
76 $item_output .= $args->after;
77
78 $output .= apply_filters('walker_nav_menu_start_el', $item_output, $item, $depth, $args);
79 }
80
81 /**
82 * Displays current element
83 */
84 function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
85 if (!$element) {
86 return;
87 }
88
89 $id_field = $this->db_fields['id'];
90
91 // Check if element has children
92 if (is_object($args[0])) {
93 $args[0]->has_children = !empty($children_elements[$element->$id_field]);
94 }
95
96 parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
97 }
98}
99
100// Use custom walker in navigation
101wp_nav_menu(array(
102 'theme_location' => 'primary',
103 'container' => false,
104 'menu_class' => 'navbar-nav',
105 'walker' => new Bootstrap_5_Walker_Nav_Menu(),
106));

WordPress Custom Menu Walker

Extend the default Walker_Nav_Menu class to create custom HTML structures for WordPress navigation menus, perfect for modern frameworks like Bootstrap or Tailwind.

// Custom Walker Class for Bootstrap 5
class Bootstrap_5_Walker_Nav_Menu extends Walker_Nav_Menu {

    /**
     * Start Level - Opening <ul>
     */
    function start_lvl(&$output, $depth = 0, $args = null) {
        $indent = str_repeat("\t", $depth);
        $classes = array('dropdown-menu');
        $class_names = implode(' ', $classes);

        $output .= "\n$indent<ul class=\"$class_names\">\n";
    }

    /**
     * Start Element - Opening <li>
     */
    function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
        $indent = ($depth) ? str_repeat("\t", $depth) : '';

        $classes = empty($item->classes) ? array() : (array) $item->classes;
        $classes[] = 'nav-item';
        $classes[] = 'menu-item-' . $item->ID;

        if ($args->walker->has_children) {
            $classes[] = 'dropdown';
        }

        if (in_array('current-menu-item', $classes)) {
            $classes[] = 'active';
        }

        $class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args));
        $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

        $id = apply_filters('nav_menu_item_id', 'menu-item-' . $item->ID, $item, $args);
        $id = $id ? ' id="' . esc_attr($id) . '"' : '';

        $output .= $indent . '<li' . $id . $class_names . '>';

        // Link attributes
        $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 : '';
        $atts['class']  = 'nav-link';

        // Dropdown toggle
        if ($args->walker->has_children) {
            $atts['class'] .= ' dropdown-toggle';
            $atts['data-bs-toggle'] = 'dropdown';
            $atts['aria-expanded'] = 'false';
        }

        // Current item
        if (in_array('current-menu-item', $item->classes)) {
            $atts['class'] .= ' active';
            $atts['aria-current'] = 'page';
        }

        $atts = apply_filters('nav_menu_link_attributes', $atts, $item, $args);

        $attributes = '';
        foreach ($atts as $attr => $value) {
            if (!empty($value)) {
                $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        $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);
    }

    /**
     * Displays current element
     */
    function display_element($element, &$children_elements, $max_depth, $depth, $args, &$output) {
        if (!$element) {
            return;
        }

        $id_field = $this->db_fields['id'];

        // Check if element has children
        if (is_object($args[0])) {
            $args[0]->has_children = !empty($children_elements[$element->$id_field]);
        }

        parent::display_element($element, $children_elements, $max_depth, $depth, $args, $output);
    }
}

// Use custom walker in navigation
wp_nav_menu(array(
    'theme_location' => 'primary',
    'container'      => false,
    'menu_class'     => 'navbar-nav',
    'walker'         => new Bootstrap_5_Walker_Nav_Menu(),
));

Tailwind CSS Walker

// Custom Walker for Tailwind CSS
class Tailwind_Walker_Nav_Menu extends Walker_Nav_Menu {

    function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
        $indent = ($depth) ? str_repeat("\t", $depth) : '';

        $classes = empty($item->classes) ? array() : (array) $item->classes;

        $class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args));

        $output .= $indent . '<li class="' . esc_attr($class_names) . '">';

        $atts = array();
        $atts['href'] = !empty($item->url) ? $item->url : '';
        $atts['class'] = 'block px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900 rounded-md transition-colors';

        if (in_array('current-menu-item', $classes)) {
            $atts['class'] .= ' bg-blue-500 text-white hover:bg-blue-600';
        }

        $attributes = '';
        foreach ($atts as $attr => $value) {
            if (!empty($value)) {
                $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        $item_output = '<a' . $attributes . '>';
        $item_output .= apply_filters('the_title', $item->title, $item->ID);
        $item_output .= '</a>';

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

Add Icons to Menu Items

// Walker with icon support
class Icon_Walker_Nav_Menu extends Walker_Nav_Menu {

    function start_el(&$output, $item, $depth = 0, $args = null, $id = 0) {
        $indent = ($depth) ? str_repeat("\t", $depth) : '';

        $classes = empty($item->classes) ? array() : (array) $item->classes;
        $class_names = join(' ', apply_filters('nav_menu_css_class', array_filter($classes), $item, $args));
        $class_names = $class_names ? ' class="' . esc_attr($class_names) . '"' : '';

        $output .= $indent . '<li' . $class_names . '>';

        $atts = array();
        $atts['href'] = !empty($item->url) ? $item->url : '';

        $attributes = '';
        foreach ($atts as $attr => $value) {
            if (!empty($value)) {
                $value = ('href' === $attr) ? esc_url($value) : esc_attr($value);
                $attributes .= ' ' . $attr . '="' . $value . '"';
            }
        }

        // Get icon from custom field
        $icon = get_post_meta($item->ID, '_menu_item_icon', true);
        $icon_html = $icon ? '<i class="' . esc_attr($icon) . '"></i> ' : '';

        $item_output = '<a' . $attributes . '>';
        $item_output .= $icon_html;
        $item_output .= apply_filters('the_title', $item->title, $item->ID);
        $item_output .= '</a>';

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

// Add custom field to menu items
function add_menu_item_icon_field($item_id, $item) {
    $icon = get_post_meta($item_id, '_menu_item_icon', true);
    ?>
    <p class="description description-wide">
        <label for="edit-menu-item-icon-<?php echo $item_id; ?>">
            Icon Class<br>
            <input type="text" id="edit-menu-item-icon-<?php echo $item_id; ?>" class="widefat" name="menu-item-icon[<?php echo $item_id; ?>]" value="<?php echo esc_attr($icon); ?>">
            <span class="description">e.g., fa fa-home or bi bi-house</span>
        </label>
    </p>
    <?php
}
add_action('wp_nav_menu_item_custom_fields', 'add_menu_item_icon_field', 10, 2);

// Save icon field
function save_menu_item_icon($menu_id, $menu_item_db_id) {
    if (isset($_POST['menu-item-icon'][$menu_item_db_id])) {
        update_post_meta($menu_item_db_id, '_menu_item_icon', sanitize_text_field($_POST['menu-item-icon'][$menu_item_db_id]));
    } else {
        delete_post_meta($menu_item_db_id, '_menu_item_icon');
    }
}
add_action('wp_update_nav_menu_item', 'save_menu_item_icon', 10, 2);

Mega Menu Walker

// Mega menu walker with columns
class Mega_Menu_Walker extends Walker_Nav_Menu {

    function start_lvl(&$output, $depth = 0, $args = null) {
        $indent = str_repeat("\t", $depth);

        if ($depth === 0) {
            // First level dropdown - mega menu
            $output .= "\n$indent<div class=\"mega-menu\">\n";
            $output .= "$indent\t<div class=\"mega-menu-inner\">\n";
            $output .= "$indent\t\t<ul class=\"mega-menu-list\">\n";
        } else {
            // Regular dropdown
            $output .= "\n$indent<ul class=\"sub-menu\">\n";
        }
    }

    function end_lvl(&$output, $depth = 0, $args = null) {
        $indent = str_repeat("\t", $depth);

        if ($depth === 0) {
            $output .= "$indent\t\t</ul>\n";
            $output .= "$indent\t</div>\n";
            $output .= "$indent</div>\n";
        } else {
            $output .= "$indent</ul>\n";
        }
    }
}

Features

  • Framework Support: Bootstrap, Tailwind, custom CSS
  • Flexible Structure: Complete control over HTML output
  • Icon Integration: Add icons to menu items
  • Mega Menus: Support for complex dropdown structures
  • Accessibility: Proper ARIA attributes
  • Custom Fields: Extend menu items with custom data

Related Snippets