Thoughts, articles and tutorials about web and design.

Auto-Generating a Select Menu in WordPress With a Custom Menu Walker

While working on the new website for BestAppSite, I wanted to have the functional <select> drop-down menu for smaller screen sizes, such as for the iPhone. These menus behave nicely and save valuable real-estate when you have many items to present.

Creating the menu itself isn’t very hard in plain HTML. The theory is to add the code for the drop-down underneath the default unordered list menu and then display/hide it using the media queries in the CSS. Couple that with the simple javascript snippet to open a URL from a drop-down selection, the menu itself is no problem.

Creating A Custom Menu Walker

By default, WordPress outputs a menu in an unordered list. To change this, you need to create a custom meny walker function. While its name makes your head spin, it is basically a PHP function that extends and thus overrides the default menu creation function.

Here is the code that I used for BestApp Site which is to be placed in your functions.php file.

 * Custom Menu Walker for Responsive Menus
 * Creates a <select> menu instead of the default
 * unordered list menus.

class Walker_Responsive_Menu extends Walker_Nav_Menu {

	function start_el(&$output, $item, $depth, $args) {
		global $wp_query;		

		// Create a visual indent in the list if we have a child item.
		$visual_indent = ( $depth ) ? str_repeat('&nbsp;&nbsp;&nbsp;&nbsp;', $depth) : '';

		// Load the item URL
		$attributes .= ! empty( $item->url ) ? ' value="'   . esc_attr( $item->url ) .'"' : '';

		$output .= '<form name="menu_selector" method="post" action="#">'; // Create the form wrapper for the URLs to work.
		$output .= '<select name="url_list" onchange="gotosite()">'; // Set up the select list and load the javascript function

		$output .= '<option value="" selected="selected" disabled="disabled">Please select...</option>'; // Create a default selected item.

		// If we have hierarchy for the item, add the indent, if not, leave it out.
		// Loop through and output each menu item as this.
		if($depth != 0) {
			$item_output .= '<option ' . $attributes .'>'. $visual_indent . apply_filters( 'the_title', $item->title, $item->ID ) . '</option>';
		} else {
			$item_output .= '<option ' . $attributes .'>'.$prepend.apply_filters( 'the_title', $item->title, $item->ID ).'</option>';

		$output .= '</select>';
		$output .= '</form>';

		// Make the output happen.
		$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

While the code looks fairly complex, it is really straight forward. I have commented what each section does in the code rather than breaking it apart here.

Applying The Walker

In order to display the new menu, we need to call it using the wp_nav_menu function. At the place in your code where you want the menu to display, simply use this code:

<?php wp_nav_menu(array(
	'theme_location' => 'primary-menu',
	'container' => false,
	'walker' => new Walker_Responsive_Menu())
); ?>

This is a basic implementation of the wp_nav_menu function that adds the custom walker to use for the output You want to change the theme location parameter to the name of the theme location that you have set up in the theme.

Conclusion and Note

It is not necessarily very complex to make WordPress generate necessary code for responsive sites as this demonstrates, all because it is well-built from the start and allows you to override default function outputs with custom walkers.

Do note that this is a very simple and almost crude walker function as it omits much of the variables required for all the array parameters for the wp_nav_menu function to work. It will however do this job elegantly.

  • Matt


    Thanks so much for sharing this! I’ve been looking for a dropdown menu for wordpress (outside of a plugin) for a while

    One question though, whats making the menu jump to the top of the page?


    • mabrahams123

      actually nevermind about my question, something else was doing that

    • Erik Bernskiold

      I’m glad you were able to figure it out! :)

  • Flow

    $output . = should be $output .=

    • Erik Bernskiold

      That is very correct. Thanks for catching the typo.