WordPress separates "where a menu can appear" (defined by the theme) from "what's in that menu" (defined by whoever's managing the site) — which is what lets a client rearrange their own navigation without ever touching a template file.
<?php
function mytheme_menus() {
register_nav_menus( [
'primary' => __( 'Primary Menu', 'mytheme' ),
'footer' => __( 'Footer Menu', 'mytheme' ),
] );
}
add_action( 'after_setup_theme', 'mytheme_menus' );Each entry is a location slug (primary, footer — referenced in template code) paired with a human-readable label (shown in the admin, where an editor assigns an actual menu to that slot from Appearance → Menus).
<?php
wp_nav_menu( [
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'primary-menu',
'fallback_cb' => false,
] );
?>| Argument | What it does |
|---|---|
| theme_location | Which registered slot to output — must match a key from register_nav_menus() |
| container | false skips the extra wrapping <div> WordPress adds by default |
| menu_class | The CSS class on the output <ul> |
| fallback_cb | false means "output nothing if no menu is assigned yet" — the alternative default tries to guess a menu from your pages, which is rarely what a finished site wants |
WordPress already adds a current-menu-item class to whichever link matches the page being viewed — no extra code needed for that part. If your CSS framework expects a different class name (commonly active), a filter renames it without touching the menu markup itself:
<?php
function mytheme_nav_active_class( $classes, $item ) {
if ( in_array( 'current-menu-item', $classes, true ) ) {
$classes[] = 'active';
}
return $classes;
}
add_filter( 'nav_menu_css_class', 'mytheme_nav_active_class', 10, 2 );Nothing shows until a menu is assigned
Until at least one menu is assigned to a location in Appearance → Menus, wp_nav_menu() with fallback_cb: false simply outputs nothing — that's expected, not a bug. Assign a menu before judging whether the navigation code actually works.
Menus handled, the next lesson looks at the Customizer — WordPress's built-in, live-preview settings screen, and a lightweight alternative to a full custom fields setup for small site-wide options.