Skip to content
WordPress

How to Add a Navigation Bar to the Header in WordPress

· · 11 min read
Add a Navigation Bar to the Header

A navigation menu that doesn’t sit in the header is a real, if uncommon, problem. Most WordPress themes wire this up automatically during setup, but a fresh theme, a heavily customized child theme, or a page built entirely inside a builder can leave you looking at a header with a logo and nothing else. Getting a menu into that space depends entirely on what you’re working with: a theme with built-in menu locations, a page builder managing its own header template, or raw header.php code with no menu hooked up at all.

Check Whether the Menu Location Already Exists First

Before writing anything, go to Appearance > Menus. If you see a menu already created with a location assigned, the theme likely supports header navigation and something else is wrong, the menu isn’t actually attached to a location, or the theme’s header template isn’t calling wp_nav_menu() for that spot.

Click Manage Locations at the top of that screen. This tab lists every menu location the active theme has registered, things like Primary, Header, or Mobile, alongside a dropdown to assign a menu to each one. If your theme only registers a location called “Footer” and nothing for the header, that’s the real answer: the theme itself has no header menu slot built in, and no amount of clicking around the Customizer will create one. That’s when you move to a page builder or manual code, covered further down.

Building the Menu Itself

Assuming a header location exists, creating the actual menu is the easy part. Appearance > Menus, then Create a New Menu. Name it something you’ll recognize later, not just “Menu 1”, since a site with several menus (header, footer, mobile) gets confusing fast without clear names.

Add pages one at a time from the panel on the left, or paste in custom links for anything that isn’t a page or post, an external URL, an anchor link to a section of the homepage, a mailto: link for a contact address. Drag items to reorder them. Dragging an item slightly right, underneath another item, nests it as a submenu, which most themes render as a dropdown on hover or tap.

Check the box for the header location under Menu Settings at the bottom, then Save Menu. If the theme has more than one location that could plausibly be “the header” (some themes split primary navigation from a secondary top-bar menu), check which one actually renders where by assigning the menu and looking at the live site rather than guessing from the label alone.

When the Theme Has No Header Menu Location At All

Lightweight or highly minimal themes sometimes skip a header menu entirely, especially ones built around a single-page layout or a landing-page-style homepage. Two real paths forward here, and they’re genuinely different in terms of long-term maintenance cost.

Switching to a theme with proper header menu support is the more sustainable choice if the current theme’s limitations go beyond just navigation, a broader mismatch between what the theme offers and what the site actually needs. Astra, GeneratePress, and Kadence all register clear header menu locations and are common replacements specifically for this reason.

Adding the menu manually through code is the other path, and it’s reasonable when the rest of the theme fits the site well and this is the one missing piece. This requires editing header.php directly, ideally inside a child theme so a future theme update doesn’t wipe the change out.

The Actual Code, and What Each Line Does

Two pieces are required: registering the menu location in functions.php, and calling that location from header.php. Skipping either one leaves you with half a working system, a location that shows up in Appearance > Menus but renders nothing, or a function call that errors out because nothing registered the location it’s asking for.

In functions.php:

function register_header_menu() {
register_nav_menus( array(
‘header-menu’ => __( ‘Header Menu’ ),
) );
}
add_action( ‘init’, ‘register_header_menu’ );

The string ‘header-menu’ is an internal identifier, it doesn’t need to match anything visible; __( ‘Header Menu’ ) is just the human-readable label that shows up in the Manage Locations screen. Keep the internal identifier short and specific if the theme might eventually need more than one menu location, since two locations both loosely named will get mixed up during future edits.

In header.php, inside the header element where the menu should render:

<nav id=”site-navigation” class=”main-navigation”>
<?php wp_nav_menu( array(
‘theme_location’ => ‘header-menu’,
‘menu_id’ => ‘primary-menu’,
‘container’ => false,
) );
?>
</nav>

‘theme_location’ has to match the identifier registered in functions.php exactly, that’s the single most common typo-driven bug in this whole process, a location registered as ‘header-menu’ but called as ‘header_menu’ with an underscore, which silently renders nothing with no error message at all. ‘container’ => false skips wrapping the output in an extra div, useful when you want the nav element itself to be the only wrapper rather than nesting another container inside it.

Page Builders Handle This Differently, and Usually More Visually

Elementor’s Theme Builder, and similar tools in Divi or Beaver Builder, let you construct a header template outside of PHP entirely, dragging a Nav Menu widget into a header section and picking which WordPress menu it should display from a dropdown. This bypasses header.php completely, the builder generates and renders its own header markup, which means code changes made to the theme’s original header.php often have zero effect once a builder-managed header template is active for a given page or the whole site.

Check which system is actually in control before troubleshooting a menu that “isn’t showing up.” If Elementor’s Theme Builder has an active header template assigned to your site, that template, not header.php, is what’s rendering. Editing the wrong file and wondering why nothing changes on the live site is a common, avoidable time sink here.

Styling the Result So It Actually Looks Like a Navigation Bar

An unstyled wp_nav_menu() output is a plain vertical list of links, technically correct, visually unusable as a header nav. Turning that into a horizontal bar takes a small amount of CSS, added through Appearance > Customize > Additional CSS or the theme’s stylesheet:

#primary-menu {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
#primary-menu li {
margin-right: 24px;
}
#primary-menu a {
text-decoration: none;
color: inherit;
padding: 8px 0;
display: block;
}

display: flex on the list turns the default stacked li elements into a horizontal row without needing floats or inline-block hacks, both of which carry their own alignment quirks that flex avoids. The padding on the anchor, not just the list item, matters for tap targets on mobile, a link with only text and no padding is a much smaller tap area than the same link with 8px of vertical padding around it.

Mobile Behavior Needs Its Own Pass

The flex row above works fine on desktop and breaks immediately on a narrow screen, six or seven menu items in a horizontal row simply don’t fit at 390px. This needs a genuinely separate mobile treatment, not just smaller font sizes on the same layout.

A hamburger toggle is the standard pattern: hide the horizontal menu below a breakpoint, show a button that toggles a collapsed menu open and closed. The CSS handles the visual hiding and showing; a small bit of JavaScript handles the actual toggle, since CSS alone can’t respond to a click event.

@media (max-width: 768px) {
.main-navigation ul {
display: none;
flex-direction: column;
}
.main-navigation.toggled ul {
display: flex;
}
}

document.querySelector(‘.menu-toggle’).addEventListener(‘click’, function() {
document.querySelector(‘.main-navigation’).classList.toggle(‘toggled’);
});

Many current themes already ship this exact pattern out of the box, worth checking the theme’s existing JS files for an existing toggle class or function before writing a duplicate one that ends up conflicting with markup the theme already expects.

A Menu With Too Many Items Is Its Own Problem

Adding every page to the header menu because they all technically exist is a common early mistake. A visitor scanning six or eight top-level items has to work harder to find the one they actually want than a visitor scanning four clearly labeled ones, with everything else organized into two or three logical dropdowns.

Group related pages under a single parent item rather than listing them all at the top level. A site with separate pages for Pricing, Plans, and Compare Plans is a strong candidate for one “Pricing” parent with those as a dropdown underneath, not three competing top-level links doing roughly the same job.

Testing the Finished Menu Properly

Click every link, don’t assume they resolve correctly just because they were built from the page list. A page renamed or moved after the menu item was created can leave a stale link pointing somewhere unexpected, especially for custom links added by pasted-in URL rather than the page picker.

Tab through the menu using only the keyboard, no mouse. Every item should be reachable and show a visible focus outline as you move through it; a menu that only works with a mouse click is a real accessibility gap, not a minor one. Check the mobile toggle at 390px specifically. Tap it open, tap a link, then confirm the menu actually closes afterward rather than staying open and covering the page content underneath.

Conditional Menus for Logged-In Members

On a community or membership site, the header often needs to show something different once a visitor logs in, a link to their profile instead of a login link, or an extra item for a members-only area that shouldn’t appear to anonymous visitors at all. WordPress doesn’t handle this through the menu builder itself; it takes a small conditional check wrapped around whichever part of the menu should change.

<?php if ( is_user_logged_in() ) : ?>
<li><a href=”<?php echo esc_url( bp_loggedin_user_domain() ); ?>”>My Profile</a></li>
<?php else : ?>
<li><a href=”<?php echo esc_url( wp_login_url() ); ?>”>Log In</a></li>
<?php endif; ?>

This snippet assumes a BuddyPress-style profile URL function is available; a plain WordPress site without that would swap in get_edit_user_link() or a custom account page URL instead. The pattern matters more than the specific function: check the login state and branch the markup accordingly, keeping both branches inside the same nav structure so the menu’s overall layout doesn’t shift depending on who’s viewing it.

A subtler version of this same problem shows up with dropdown submenus that should only appear for certain roles, an admin-only settings link nested under an account dropdown, for instance. Filtering wp_nav_menu_objects lets you remove specific items by their menu item ID based on current_user_can() checks, which keeps the menu structure itself defined once in Appearance > Menus while still hiding role-specific items from users who shouldn’t see them.

Making Dropdown Submenus Actually Accessible

A dropdown that only opens on mouse hover fails for two overlapping groups of visitors: anyone navigating by keyboard, and anyone on a touchscreen device where “hover” doesn’t really exist as an interaction. Both need a working alternative, and CSS-only hover dropdowns don’t provide one on their own.

The core fix is adding aria-expanded to the parent link, toggled between “true” and “false” by a small script whenever the submenu opens or closes, paired with aria-haspopup=”true” on the same element so screen readers announce that a submenu exists before the user even opens it.

document.querySelectorAll(‘.menu-item-has-children > a’).forEach(function(link) {
link.setAttribute(‘aria-haspopup’, ‘true’);
link.setAttribute(‘aria-expanded’, ‘false’);
link.addEventListener(‘focus’, function() {
this.setAttribute(‘aria-expanded’, ‘true’);
});
});

This is a genuinely small addition on top of a menu that already works visually, and it’s the difference between a dropdown that’s cosmetically fine and one that actually functions for someone tabbing through the page with a keyboard rather than clicking with a mouse. Test it the same way you’d test anything keyboard-dependent: unplug the mouse and tab through the header, confirming every submenu opens and every link inside it is reachable in a sensible order.

Common Mistakes Worth Naming Directly

Registering the menu location string one way in functions.php and referencing it slightly differently in the theme_location argument, which fails silently with no visible error.

Editing header.php directly on a parent theme instead of a child theme, then losing the change entirely on the next theme update.

Building a header menu manually in code while a page builder’s own header template is actually the one rendering on the live site, so nothing changes no matter what gets edited.

Styling only the desktop layout and never building a real mobile toggle, leaving a cramped, overflowing row of links on any screen under 768px.

A Real Troubleshooting Scenario: The Menu Works Locally, Not on Staging

A developer builds the header menu on a local environment, everything works, submenus open, mobile toggle functions correctly. It gets pushed to staging for review, and the client reports the menu is just a plain bulleted list, no styling, no dropdown behavior at all.

The instinct is to assume the CSS didn’t deploy. Check that first, confirm the stylesheet actually loaded on staging by looking at the Network tab, not just assuming a git push moved everything. If the CSS is loading correctly and the menu still looks wrong, the more likely culprit is a caching plugin serving a stale version of the header from before the menu changes were made, common on staging environments that clone production’s cache state along with everything else.

Clear the staging site’s page cache explicitly rather than assuming a fresh deploy invalidates it automatically; many caching plugins don’t tie cache invalidation to file changes made outside the WordPress admin, which is exactly how a code-based deploy typically happens. If clearing cache doesn’t resolve it, check whether staging is actually pointed at a different theme or a different active menu location than production, a surprisingly common mismatch when a staging environment was cloned from an older backup before the header work started.

FAQ

Why does my menu show up in Appearance > Menus but not on the live site?
Almost always a location mismatch, either the menu isn’t assigned to any location under Manage Locations, or the theme’s header.php is calling a theme_location string that doesn’t match what was registered.

Can I have a different menu for mobile than desktop?
Yes, register a second location (mobile-menu, for instance) and assign a separate, usually shorter, menu to it, then use CSS to show only the appropriate one per breakpoint.

Does adding a navigation menu actually help SEO?
Indirectly. A well-structured menu gives search engines clear internal links to your important pages and helps visitors find content instead of leaving, both of which support SEO, though the menu itself isn’t a direct ranking factor on its own.

My dropdown submenu isn’t showing on hover. What’s wrong?
Usually a CSS issue where the submenu’s default display is set to none with nothing un-hiding it on :hover or on the JavaScript-added open class for touch devices. Inspect the submenu element directly to see which rule is actually suppressing it.

Can I use a different menu location for a translated version of my site?
Yes, most multilingual plugins let you assign a separate menu per language to the same theme location, switching which one renders based on the active language. Register the location once in functions.php; the multilingual plugin handles swapping the actual menu content per language from there.

Where This Leaves You

Check Manage Locations first. If a header location already exists, the whole task is a five-minute menu build, no code required.

If it doesn’t exist, decide between a theme switch and a manual code addition based on how well the rest of the theme actually fits the site, not just this one missing feature.

Style it as a real horizontal bar and build a genuine mobile toggle. Test with a keyboard before calling it finished, since a navigation menu that only works with a mouse on a wide screen isn’t actually done.