“Make it so the client can change the menu items themselves”—that requirement is met with two functions: register_nav_menus() and wp_nav_menu(). It takes three steps.
Step 1: register the menu location in your theme
function my_register_menus() {
register_nav_menus([
'global' => 'Header menu',
'footer' => 'Footer menu',
]);
}
add_action('after_setup_theme', 'my_register_menus');The key (global) is the name you call from templates; the value is the label shown in the admin screen.
Step 2: call it in a template
<?php
wp_nav_menu([
'theme_location' => 'global',
'container' => false, // don't output a wrapping div
'items_wrap' => '<ul class="global-nav">%3$s</ul>',
'depth' => 2,
]);
?>Step 3: create the menu in the admin screen
- Open “Appearance” → “Menus”
- “Create a new menu” → enter a name → “Create Menu”
- Add pages, categories and custom links from the left and drag them into order
- Under “Menu Settings” → “Display location,” tick “Header menu”
- “Save Menu”
Cheat sheet of arguments
| Argument | Role | Example |
|---|---|---|
theme_location | Which location’s menu | 'global' |
container | The wrapping element | false (none) / 'nav' |
container_class | Class on the wrapper | 'nav-wrap' |
menu_class | Class on the ul | 'global-nav' |
items_wrap | Markup for the ul portion | '<ul class="x">%3$s</ul>' |
depth | How many levels to output | 1 (no children) / 2 |
fallback_cb | Fallback when no menu is set | false (output nothing) |
walker | Fine-grained control of output | new My_Walker() |
Using the classes WordPress adds
WordPress adds classes describing state to each li. You can build behavior with CSS alone.
| Class | When it’s added |
|---|---|
current-menu-item | The item for the page currently displayed |
current-menu-parent / current-menu-ancestor | Its parent items |
menu-item-has-children | An item that has a submenu |
menu-item-{ID} | A unique class per item |
.global-nav { display: flex; gap: 1.5rem; list-style: none; }
.global-nav a { text-decoration: none; padding: 0.5em 0; display: inline-block; }
.global-nav .current-menu-item > a { border-bottom: 2px solid currentColor; font-weight: 700; }Dropdowns (submenus)
Dragging an item to the right in the admin screen makes it a child. You can turn that into a dropdown with CSS alone.
.global-nav .menu-item-has-children { position: relative; }
.global-nav .sub-menu {
position: absolute;
top: 100%;
left: 0;
min-width: 12em;
list-style: none;
background: #fff;
box-shadow: 0 8px 24px rgb(0 0 0 / 0.12);
border-radius: 8px;
padding: 0.5rem 0;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease;
}
.global-nav .menu-item-has-children:hover .sub-menu,
.global-nav .menu-item-has-children:focus-within .sub-menu {
opacity: 1;
visibility: visible;
}Including :focus-within means it opens for keyboard users too. Not assuming a mouse is the considerate way to build it.
A hamburger menu for phones
<button class="menu-toggle" aria-expanded="false" aria-controls="global-nav">
<span class="sr-only">Open menu</span>☰
</button>
<?php wp_nav_menu([
'theme_location' => 'global',
'container' => false,
'items_wrap' => '<ul id="global-nav" class="global-nav">%3$s</ul>',
]); ?>const toggle = document.querySelector('.menu-toggle');
const nav = document.getElementById('global-nav');
toggle?.addEventListener('click', () => {
const open = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', String(!open));
nav.classList.toggle('is-open');
});Toggling aria-expanded communicates the open/closed state to assistive technology as well.
Building the menu HTML entirely yourself
When matching wp_nav_menu()’s output is more trouble than it’s worth, you can fetch just the item data and build your own.
<?php
$locations = get_nav_menu_locations();
$items = !empty($locations['global']) ? wp_get_nav_menu_items($locations['global']) : [];
?>
<?php if ($items) : ?>
<ul class="global-nav">
<?php foreach ($items as $item) : ?>
<?php if ((int) $item->menu_item_parent === 0) : ?>
<li>
<a href="<?php echo esc_url($item->url); ?>"><?php echo esc_html($item->title); ?></a>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>Items whose menu_item_parent is 0 are the top level. You get complete control of the structure, but in exchange you’re responsible for things like the current-page class.
Listing pages without using menus
On a small site where “just list the static pages automatically” is enough, this is easier.
<?php wp_list_pages(['title_li' => '', 'depth' => 1]); ?>But the order and which pages appear can’t be chosen from the admin screen, so on a site you’re delivering, the menu feature is the kinder option.
Theme developmentA navigation menu you can edit from the admin screenWant to learn this as part of building a theme? Head to this lesson of the theme development courseSummary
- Register locations with
register_nav_menus()and call them withwp_nav_menu() - Nothing appears until you tick “Display location” in the admin screen
- Remove unwanted markup with
container: falseanditems_wrap - Use the automatic classes like
current-menu-itemin CSS to build highlighting and dropdowns
Leaving menus editable from the admin screen means “please add one link” stops arriving as a job for you.