SponsoredHosinger Special Offer

WP WordPress Theme Development

Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.

Navigation Menus

WordPress Theme Development Lesson 13 of 30 ~4 min read

Overview

Navigation menus in a classic theme are built in two halves: the theme registers named menu locations with register_nav_menus(), and prints whatever menu the site owner assigned to a location with wp_nav_menu().

This separation is the point. You decide where menus can go; the site owner decides what is in them, from Appearance → Menus, without touching code.

Step 1: register locations

<?php
function gdc_starter_menus() {
    register_nav_menus(
        array(
            'primary' => esc_html__( 'Primary Menu', 'gdc-starter' ),
            'footer'  => esc_html__( 'Footer Menu', 'gdc-starter' ),
            'social'  => esc_html__( 'Social Links Menu', 'gdc-starter' ),
        )
    );
}
add_action( 'after_setup_theme', 'gdc_starter_menus' );

Each array key — primary — is the location ID your template uses. Each value is the label the site owner sees.

Step 2: print a menu

<nav class="main-navigation" aria-label="<?php esc_attr_e( 'Primary', 'gdc-starter' ); ?>">
    <button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
        <?php esc_html_e( 'Menu', 'gdc-starter' ); ?>
    </button>

    <?php
    wp_nav_menu(
        array(
            'theme_location' => 'primary',
            'menu_id'        => 'primary-menu',
            'menu_class'     => 'menu',
            'container'      => false,
            'depth'          => 2,
            'fallback_cb'    => false,
        )
    );
    ?>
</nav>

The arguments that matter

ArgumentEffect
theme_locationWhich registered location to print.
containerWrapper element. false removes the default <div>, since you already have <nav>.
menu_class / menu_idClass and ID on the <ul>.
depthHow many levels of sub-menus to show. 0 means unlimited.
fallback_cbWhat to print when no menu is assigned. The default lists every page — usually unwanted. false prints nothing.

Checking a location has a menu

Wrap optional menus so their surrounding markup disappears when nothing is assigned:

<?php if ( has_nav_menu( 'footer' ) ) : ?>
    <nav class="footer-navigation" aria-label="<?php esc_attr_e( 'Footer', 'gdc-starter' ); ?>">
        <?php
        wp_nav_menu(
            array(
                'theme_location' => 'footer',
                'container'      => false,
                'depth'          => 1,
            )
        );
        ?>
    </nav>
<?php endif; ?>

Styling the active page

WordPress adds classes to menu items automatically. The current page gets current-menu-item, and its parents get current-menu-ancestor. Items with sub-menus get menu-item-has-children.

.main-navigation .menu {
    display: flex;
    gap: 24px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.main-navigation .current-menu-item > a,
.main-navigation .current-menu-ancestor > a {
    color: #6c63ff;
    font-weight: 700;
}

.main-navigation .sub-menu {
    display: none;
}

.main-navigation .menu-item-has-children:focus-within > .sub-menu,
.main-navigation .menu-item-has-children:hover > .sub-menu {
    display: block;
}
Tip: Use :focus-within, not only :hover, to reveal sub-menus. Keyboard users never hover, so a hover-only menu is unreachable for them.

Adding classes to each link

Need a class on every <a> for a CSS framework? Filter it rather than replacing the whole menu output:

<?php
function gdc_starter_menu_link_class( $atts, $item, $args ) {
    if ( 'primary' === $args->theme_location ) {
        $atts['class'] = 'nav-link';
    }
    return $atts;
}
add_filter( 'nav_menu_link_attributes', 'gdc_starter_menu_link_class', 10, 3 );

Practice

  • Register three locations, build a menu with a sub-menu in Appearance → Menus, and assign it to primary.
  • Print it with container => false and style current-menu-item.
  • Tab through the menu with the keyboard and confirm sub-menus open on focus.
Create a free account to save which lessons you've finished. Save my progress