mobile-menu.php

I’ll build this as a production-style mobile-menu.php template part for your RX theme: accessible menu, off-canvas drawer, search, dark mode hooks, social links, WooCommerce/account support, language area, schema-friendly markup, and safe WordPress escaping.

Below is a very advanced rx-theme/template-parts/header/mobile-menu.php file. It includes: mobile drawer, overlay, accessibility, search, menu fallback, secondary menu, account links, WooCommerce cart support, dark-mode button hook, language/currency hooks, social links, contact area, widget area, close buttons, no-JS fallback support, and safe escaping.

WordPress recommends wp_nav_menu() for theme menus, and get_search_form() can load your theme search form or the default search form. Escaping functions like esc_attr() help make output safer in attributes.

Create this file:

<?php
/**
 * RX Theme Mobile Menu Template Part
 *
 * File: template-parts/header/mobile-menu.php
 *
 * @package RX_Theme
 * @since 1.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * --------------------------------------------------------------------------
 * Mobile Menu Settings
 * --------------------------------------------------------------------------
 */

$rx_mobile_menu_id        = 'rx-mobile-menu';
$rx_mobile_drawer_id      = 'rx-mobile-drawer';
$rx_mobile_overlay_id     = 'rx-mobile-overlay';
$rx_mobile_search_id      = 'rx-mobile-search';
$rx_mobile_account_id     = 'rx-mobile-account';
$rx_mobile_widget_area_id = 'mobile-menu-drawer';

$rx_site_name        = get_bloginfo( 'name' );
$rx_site_description = get_bloginfo( 'description' );
$rx_home_url         = home_url( '/' );

$rx_has_mobile_menu           = has_nav_menu( 'mobile' );
$rx_has_primary_menu          = has_nav_menu( 'primary' );
$rx_has_mobile_secondary_menu = has_nav_menu( 'mobile-secondary' );
$rx_has_footer_menu           = has_nav_menu( 'footer' );

$rx_enable_search      = apply_filters( 'rx_theme_mobile_menu_enable_search', true );
$rx_enable_account     = apply_filters( 'rx_theme_mobile_menu_enable_account', true );
$rx_enable_cart        = apply_filters( 'rx_theme_mobile_menu_enable_cart', true );
$rx_enable_social      = apply_filters( 'rx_theme_mobile_menu_enable_social', true );
$rx_enable_contact     = apply_filters( 'rx_theme_mobile_menu_enable_contact', true );
$rx_enable_dark_toggle = apply_filters( 'rx_theme_mobile_menu_enable_dark_toggle', true );
$rx_enable_language    = apply_filters( 'rx_theme_mobile_menu_enable_language', true );
$rx_enable_widgets     = apply_filters( 'rx_theme_mobile_menu_enable_widgets', true );
$rx_enable_quick_links = apply_filters( 'rx_theme_mobile_menu_enable_quick_links', true );

/**
 * Social links from Customizer theme mods.
 * You can create these theme mods later:
 * rx_social_facebook, rx_social_x, rx_social_linkedin, rx_social_youtube,
 * rx_social_instagram, rx_social_pinterest, rx_social_tiktok, rx_social_whatsapp.
 */
$rx_social_links = apply_filters(
	'rx_theme_mobile_social_links',
	array(
		'facebook'  => get_theme_mod( 'rx_social_facebook', '' ),
		'x'         => get_theme_mod( 'rx_social_x', '' ),
		'linkedin'  => get_theme_mod( 'rx_social_linkedin', '' ),
		'youtube'   => get_theme_mod( 'rx_social_youtube', '' ),
		'instagram' => get_theme_mod( 'rx_social_instagram', '' ),
		'pinterest' => get_theme_mod( 'rx_social_pinterest', '' ),
		'tiktok'    => get_theme_mod( 'rx_social_tiktok', '' ),
		'whatsapp'  => get_theme_mod( 'rx_social_whatsapp', '' ),
	)
);

$rx_contact_email = apply_filters( 'rx_theme_mobile_contact_email', get_theme_mod( 'rx_contact_email', get_option( 'admin_email' ) ) );
$rx_contact_phone = apply_filters( 'rx_theme_mobile_contact_phone', get_theme_mod( 'rx_contact_phone', '' ) );
$rx_contact_text  = apply_filters( 'rx_theme_mobile_contact_text', get_theme_mod( 'rx_contact_text', __( 'Need help? Contact us anytime.', 'rx-theme' ) ) );

$rx_quick_links = apply_filters(
	'rx_theme_mobile_quick_links',
	array(
		array(
			'label' => __( 'Home', 'rx-theme' ),
			'url'   => home_url( '/' ),
			'icon'  => 'home',
		),
		array(
			'label' => __( 'Blog', 'rx-theme' ),
			'url'   => get_permalink( get_option( 'page_for_posts' ) ) ? get_permalink( get_option( 'page_for_posts' ) ) : home_url( '/' ),
			'icon'  => 'blog',
		),
		array(
			'label' => __( 'Contact', 'rx-theme' ),
			'url'   => home_url( '/contact/' ),
			'icon'  => 'contact',
		),
	)
);

$rx_cart_count = 0;
$rx_cart_url   = '';

if ( class_exists( 'WooCommerce' ) && function_exists( 'WC' ) && WC()->cart ) {
	$rx_cart_count = WC()->cart->get_cart_contents_count();
	$rx_cart_url   = wc_get_cart_url();
}

$rx_account_url = is_user_logged_in() ? get_edit_profile_url( get_current_user_id() ) : wp_login_url( esc_url_raw( add_query_arg( array(), home_url( add_query_arg( null, null ) ) ) ) );

if ( class_exists( 'WooCommerce' ) && function_exists( 'wc_get_page_permalink' ) ) {
	$rx_account_url = wc_get_page_permalink( 'myaccount' );
}

?>

<div
	id="<?php echo esc_attr( $rx_mobile_menu_id ); ?>"
	class="rx-mobile-menu"
	data-rx-component="mobile-menu"
	data-rx-open="false"
>
	<button
		type="button"
		class="rx-mobile-menu__toggle"
		aria-controls="<?php echo esc_attr( $rx_mobile_drawer_id ); ?>"
		aria-expanded="false"
		aria-label="<?php echo esc_attr__( 'Open mobile menu', 'rx-theme' ); ?>"
		data-rx-mobile-open
	>
		<span class="rx-mobile-menu__toggle-icon" aria-hidden="true">
			<span class="rx-mobile-menu__bar"></span>
			<span class="rx-mobile-menu__bar"></span>
			<span class="rx-mobile-menu__bar"></span>
		</span>
		<span class="rx-mobile-menu__toggle-text">
			<?php esc_html_e( 'Menu', 'rx-theme' ); ?>
		</span>
	</button>

	<div
		id="<?php echo esc_attr( $rx_mobile_overlay_id ); ?>"
		class="rx-mobile-menu__overlay"
		hidden
		data-rx-mobile-close
	></div>

	<aside
		id="<?php echo esc_attr( $rx_mobile_drawer_id ); ?>"
		class="rx-mobile-menu__drawer"
		role="dialog"
		aria-modal="true"
		aria-labelledby="rx-mobile-menu-title"
		aria-hidden="true"
		tabindex="-1"
	>
		<div class="rx-mobile-menu__inner">

			<header class="rx-mobile-menu__header">
				<div class="rx-mobile-menu__brand">
					<a class="rx-mobile-menu__logo-link" href="<?php echo esc_url( $rx_home_url ); ?>" rel="home">
						<?php
						if ( has_custom_logo() ) {
							the_custom_logo();
						} else {
							?>
							<span class="rx-mobile-menu__site-title">
								<?php echo esc_html( $rx_site_name ); ?>
							</span>
							<?php if ( ! empty( $rx_site_description ) ) : ?>
								<span class="rx-mobile-menu__site-description">
									<?php echo esc_html( $rx_site_description ); ?>
								</span>
							<?php endif; ?>
							<?php
						}
						?>
					</a>
				</div>

				<button
					type="button"
					class="rx-mobile-menu__close"
					aria-label="<?php echo esc_attr__( 'Close mobile menu', 'rx-theme' ); ?>"
					data-rx-mobile-close
				>
					<span aria-hidden="true">&times;</span>
				</button>
			</header>

			<div class="rx-mobile-menu__body">

				<?php if ( $rx_enable_search ) : ?>
					<section
						id="<?php echo esc_attr( $rx_mobile_search_id ); ?>"
						class="rx-mobile-menu__section rx-mobile-menu__search-section"
						aria-label="<?php echo esc_attr__( 'Mobile search', 'rx-theme' ); ?>"
					>
						<h2 class="screen-reader-text">
							<?php esc_html_e( 'Search this website', 'rx-theme' ); ?>
						</h2>

						<div class="rx-mobile-menu__search-wrap">
							<?php
							get_search_form(
								array(
									'aria_label' => __( 'Mobile search form', 'rx-theme' ),
								)
							);
							?>
						</div>
					</section>
				<?php endif; ?>

				<?php if ( $rx_enable_quick_links && ! empty( $rx_quick_links ) ) : ?>
					<section class="rx-mobile-menu__section rx-mobile-menu__quick-links-section">
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'Quick Links', 'rx-theme' ); ?>
						</h2>

						<ul class="rx-mobile-menu__quick-links">
							<?php foreach ( $rx_quick_links as $rx_quick_link ) : ?>
								<?php
								$rx_quick_label = isset( $rx_quick_link['label'] ) ? $rx_quick_link['label'] : '';
								$rx_quick_url   = isset( $rx_quick_link['url'] ) ? $rx_quick_link['url'] : '';
								$rx_quick_icon  = isset( $rx_quick_link['icon'] ) ? $rx_quick_link['icon'] : 'link';

								if ( empty( $rx_quick_label ) || empty( $rx_quick_url ) ) {
									continue;
								}
								?>
								<li class="rx-mobile-menu__quick-link-item">
									<a class="rx-mobile-menu__quick-link" href="<?php echo esc_url( $rx_quick_url ); ?>">
										<span class="rx-mobile-menu__quick-link-icon rx-icon-<?php echo esc_attr( sanitize_html_class( $rx_quick_icon ) ); ?>" aria-hidden="true"></span>
										<span class="rx-mobile-menu__quick-link-label">
											<?php echo esc_html( $rx_quick_label ); ?>
										</span>
									</a>
								</li>
							<?php endforeach; ?>
						</ul>
					</section>
				<?php endif; ?>

				<nav
					class="rx-mobile-menu__nav"
					aria-label="<?php echo esc_attr__( 'Mobile primary navigation', 'rx-theme' ); ?>"
				>
					<h2 id="rx-mobile-menu-title" class="rx-mobile-menu__section-title">
						<?php esc_html_e( 'Navigation', 'rx-theme' ); ?>
					</h2>

					<?php
					if ( $rx_has_mobile_menu ) {
						wp_nav_menu(
							array(
								'theme_location'  => 'mobile',
								'menu_id'         => 'rx-mobile-primary-menu',
								'menu_class'      => 'rx-mobile-menu__list rx-mobile-menu__list--primary',
								'container'       => false,
								'depth'           => 4,
								'fallback_cb'     => false,
								'link_before'     => '<span class="rx-mobile-menu__link-text">',
								'link_after'      => '</span>',
								'items_wrap'      => '<ul id="%1$s" class="%2$s" role="list">%3$s</ul>',
							)
						);
					} elseif ( $rx_has_primary_menu ) {
						wp_nav_menu(
							array(
								'theme_location'  => 'primary',
								'menu_id'         => 'rx-mobile-primary-menu',
								'menu_class'      => 'rx-mobile-menu__list rx-mobile-menu__list--primary',
								'container'       => false,
								'depth'           => 4,
								'fallback_cb'     => false,
								'link_before'     => '<span class="rx-mobile-menu__link-text">',
								'link_after'      => '</span>',
								'items_wrap'      => '<ul id="%1$s" class="%2$s" role="list">%3$s</ul>',
							)
						);
					} else {
						wp_page_menu(
							array(
								'menu_class'  => 'rx-mobile-menu__page-list',
								'show_home'   => true,
								'depth'       => 3,
								'before'      => '<div class="rx-mobile-menu__fallback">',
								'after'       => '</div>',
								'link_before' => '<span class="rx-mobile-menu__link-text">',
								'link_after'  => '</span>',
							)
						);
					}
					?>
				</nav>

				<?php if ( $rx_has_mobile_secondary_menu || $rx_has_footer_menu ) : ?>
					<nav
						class="rx-mobile-menu__nav rx-mobile-menu__nav--secondary"
						aria-label="<?php echo esc_attr__( 'Mobile secondary navigation', 'rx-theme' ); ?>"
					>
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'More Pages', 'rx-theme' ); ?>
						</h2>

						<?php
						if ( $rx_has_mobile_secondary_menu ) {
							wp_nav_menu(
								array(
									'theme_location' => 'mobile-secondary',
									'menu_id'        => 'rx-mobile-secondary-menu',
									'menu_class'     => 'rx-mobile-menu__list rx-mobile-menu__list--secondary',
									'container'      => false,
									'depth'          => 2,
									'fallback_cb'    => false,
									'link_before'    => '<span class="rx-mobile-menu__link-text">',
									'link_after'     => '</span>',
									'items_wrap'     => '<ul id="%1$s" class="%2$s" role="list">%3$s</ul>',
								)
							);
						} elseif ( $rx_has_footer_menu ) {
							wp_nav_menu(
								array(
									'theme_location' => 'footer',
									'menu_id'        => 'rx-mobile-secondary-menu',
									'menu_class'     => 'rx-mobile-menu__list rx-mobile-menu__list--secondary',
									'container'      => false,
									'depth'          => 2,
									'fallback_cb'    => false,
									'link_before'    => '<span class="rx-mobile-menu__link-text">',
									'link_after'     => '</span>',
									'items_wrap'     => '<ul id="%1$s" class="%2$s" role="list">%3$s</ul>',
								)
							);
						}
						?>
					</nav>
				<?php endif; ?>

				<?php if ( $rx_enable_account || $rx_enable_cart || $rx_enable_dark_toggle || $rx_enable_language ) : ?>
					<section
						id="<?php echo esc_attr( $rx_mobile_account_id ); ?>"
						class="rx-mobile-menu__section rx-mobile-menu__actions-section"
					>
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'Tools', 'rx-theme' ); ?>
						</h2>

						<ul class="rx-mobile-menu__actions" role="list">
							<?php if ( $rx_enable_account ) : ?>
								<li class="rx-mobile-menu__action-item">
									<a class="rx-mobile-menu__action-link rx-mobile-menu__action-link--account" href="<?php echo esc_url( $rx_account_url ); ?>">
										<span class="rx-mobile-menu__action-icon rx-icon-user" aria-hidden="true"></span>
										<span class="rx-mobile-menu__action-label">
											<?php echo is_user_logged_in() ? esc_html__( 'My Account', 'rx-theme' ) : esc_html__( 'Login / Register', 'rx-theme' ); ?>
										</span>
									</a>
								</li>
							<?php endif; ?>

							<?php if ( $rx_enable_cart && ! empty( $rx_cart_url ) ) : ?>
								<li class="rx-mobile-menu__action-item">
									<a class="rx-mobile-menu__action-link rx-mobile-menu__action-link--cart" href="<?php echo esc_url( $rx_cart_url ); ?>">
										<span class="rx-mobile-menu__action-icon rx-icon-cart" aria-hidden="true"></span>
										<span class="rx-mobile-menu__action-label">
											<?php esc_html_e( 'Cart', 'rx-theme' ); ?>
										</span>
										<span class="rx-mobile-menu__cart-count">
											<?php echo esc_html( number_format_i18n( absint( $rx_cart_count ) ) ); ?>
										</span>
									</a>
								</li>
							<?php endif; ?>

							<?php if ( $rx_enable_dark_toggle ) : ?>
								<li class="rx-mobile-menu__action-item">
									<button
										type="button"
										class="rx-mobile-menu__action-button rx-mobile-menu__action-button--theme"
										aria-pressed="false"
										data-rx-theme-toggle
									>
										<span class="rx-mobile-menu__action-icon rx-icon-moon" aria-hidden="true"></span>
										<span class="rx-mobile-menu__action-label">
											<?php esc_html_e( 'Dark Mode', 'rx-theme' ); ?>
										</span>
									</button>
								</li>
							<?php endif; ?>

							<?php if ( $rx_enable_language ) : ?>
								<li class="rx-mobile-menu__action-item rx-mobile-menu__action-item--language">
									<div class="rx-mobile-menu__language">
										<?php
										/**
										 * Hook for WPML, Polylang, TranslatePress, GTranslate, currency switcher, etc.
										 *
										 * Example:
										 * add_action( 'rx_theme_mobile_language_switcher', 'your_language_function' );
										 */
										do_action( 'rx_theme_mobile_language_switcher' );
										?>
									</div>
								</li>
							<?php endif; ?>
						</ul>
					</section>
				<?php endif; ?>

				<?php if ( $rx_enable_social && ! empty( array_filter( $rx_social_links ) ) ) : ?>
					<section class="rx-mobile-menu__section rx-mobile-menu__social-section">
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'Follow Us', 'rx-theme' ); ?>
						</h2>

						<ul class="rx-mobile-menu__social-list" role="list">
							<?php foreach ( $rx_social_links as $rx_social_name => $rx_social_url ) : ?>
								<?php
								if ( empty( $rx_social_url ) ) {
									continue;
								}

								$rx_social_label = ucfirst( str_replace( array( '-', '_' ), ' ', $rx_social_name ) );
								?>
								<li class="rx-mobile-menu__social-item">
									<a
										class="rx-mobile-menu__social-link rx-mobile-menu__social-link--<?php echo esc_attr( sanitize_html_class( $rx_social_name ) ); ?>"
										href="<?php echo esc_url( $rx_social_url ); ?>"
										target="_blank"
										rel="noopener noreferrer nofollow"
										aria-label="<?php echo esc_attr( sprintf( __( 'Follow %1$s on %2$s', 'rx-theme' ), $rx_site_name, $rx_social_label ) ); ?>"
									>
										<span class="rx-mobile-menu__social-icon rx-icon-<?php echo esc_attr( sanitize_html_class( $rx_social_name ) ); ?>" aria-hidden="true"></span>
										<span class="rx-mobile-menu__social-text">
											<?php echo esc_html( $rx_social_label ); ?>
										</span>
									</a>
								</li>
							<?php endforeach; ?>
						</ul>
					</section>
				<?php endif; ?>

				<?php if ( $rx_enable_contact && ( ! empty( $rx_contact_email ) || ! empty( $rx_contact_phone ) || ! empty( $rx_contact_text ) ) ) : ?>
					<section class="rx-mobile-menu__section rx-mobile-menu__contact-section">
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'Contact', 'rx-theme' ); ?>
						</h2>

						<?php if ( ! empty( $rx_contact_text ) ) : ?>
							<p class="rx-mobile-menu__contact-text">
								<?php echo esc_html( $rx_contact_text ); ?>
							</p>
						<?php endif; ?>

						<ul class="rx-mobile-menu__contact-list" role="list">
							<?php if ( ! empty( $rx_contact_email ) ) : ?>
								<li class="rx-mobile-menu__contact-item">
									<a class="rx-mobile-menu__contact-link" href="mailto:<?php echo esc_attr( antispambot( $rx_contact_email ) ); ?>">
										<span class="rx-mobile-menu__contact-icon rx-icon-email" aria-hidden="true"></span>
										<span><?php echo esc_html( antispambot( $rx_contact_email ) ); ?></span>
									</a>
								</li>
							<?php endif; ?>

							<?php if ( ! empty( $rx_contact_phone ) ) : ?>
								<li class="rx-mobile-menu__contact-item">
									<a class="rx-mobile-menu__contact-link" href="tel:<?php echo esc_attr( preg_replace( '/[^0-9+]/', '', $rx_contact_phone ) ); ?>">
										<span class="rx-mobile-menu__contact-icon rx-icon-phone" aria-hidden="true"></span>
										<span><?php echo esc_html( $rx_contact_phone ); ?></span>
									</a>
								</li>
							<?php endif; ?>
						</ul>
					</section>
				<?php endif; ?>

				<?php if ( $rx_enable_widgets && is_active_sidebar( $rx_mobile_widget_area_id ) ) : ?>
					<section class="rx-mobile-menu__section rx-mobile-menu__widgets-section">
						<h2 class="rx-mobile-menu__section-title">
							<?php esc_html_e( 'More', 'rx-theme' ); ?>
						</h2>

						<div class="rx-mobile-menu__widgets">
							<?php dynamic_sidebar( $rx_mobile_widget_area_id ); ?>
						</div>
					</section>
				<?php endif; ?>

			</div>

			<footer class="rx-mobile-menu__footer">
				<p class="rx-mobile-menu__copyright">
					&copy; <?php echo esc_html( date_i18n( 'Y' ) ); ?>
					<a href="<?php echo esc_url( $rx_home_url ); ?>">
						<?php echo esc_html( $rx_site_name ); ?>
					</a>
				</p>

				<button
					type="button"
					class="rx-mobile-menu__back-to-top"
					data-rx-back-to-top
				>
					<?php esc_html_e( 'Back to Top', 'rx-theme' ); ?>
				</button>
			</footer>

		</div>
	</aside>

	<noscript>
		<style>
			.rx-mobile-menu__drawer {
				position: static;
				visibility: visible;
				opacity: 1;
				transform: none;
				height: auto;
				max-height: none;
			}

			.rx-mobile-menu__toggle,
			.rx-mobile-menu__close,
			.rx-mobile-menu__overlay {
				display: none;
			}
		</style>
	</noscript>
</div>

<script>
/**
 * RX Theme Mobile Menu Script
 * You can move this later to assets/js/mobile-menu.js for better performance.
 */
(function () {
	'use strict';

	var root = document.getElementById('<?php echo esc_js( $rx_mobile_menu_id ); ?>');

	if (!root) {
		return;
	}

	var drawer = document.getElementById('<?php echo esc_js( $rx_mobile_drawer_id ); ?>');
	var overlay = document.getElementById('<?php echo esc_js( $rx_mobile_overlay_id ); ?>');
	var openButtons = root.querySelectorAll('[data-rx-mobile-open]');
	var closeButtons = root.querySelectorAll('[data-rx-mobile-close]');
	var themeToggle = root.querySelector('[data-rx-theme-toggle]');
	var backToTop = root.querySelector('[data-rx-back-to-top]');
	var focusableSelectors = [
		'a[href]',
		'button:not([disabled])',
		'input:not([disabled])',
		'select:not([disabled])',
		'textarea:not([disabled])',
		'[tabindex]:not([tabindex="-1"])'
	].join(',');

	var lastFocusedElement = null;

	function getFocusableElements() {
		if (!drawer) {
			return [];
		}

		return Array.prototype.slice.call(drawer.querySelectorAll(focusableSelectors))
			.filter(function (element) {
				return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
			});
	}

	function setExpandedState(isOpen) {
		Array.prototype.forEach.call(openButtons, function (button) {
			button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
		});

		root.setAttribute('data-rx-open', isOpen ? 'true' : 'false');

		if (drawer) {
			drawer.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
		}

		if (overlay) {
			if (isOpen) {
				overlay.removeAttribute('hidden');
			} else {
				overlay.setAttribute('hidden', '');
			}
		}

		document.documentElement.classList.toggle('rx-mobile-menu-is-open', isOpen);
		document.body.classList.toggle('rx-mobile-menu-is-open', isOpen);
	}

	function openMenu() {
		lastFocusedElement = document.activeElement;

		setExpandedState(true);

		window.setTimeout(function () {
			var focusableElements = getFocusableElements();

			if (focusableElements.length) {
				focusableElements[0].focus();
			} else if (drawer) {
				drawer.focus();
			}
		}, 30);
	}

	function closeMenu() {
		setExpandedState(false);

		if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') {
			lastFocusedElement.focus();
		}
	}

	function trapFocus(event) {
		if (!root || root.getAttribute('data-rx-open') !== 'true') {
			return;
		}

		if (event.key !== 'Tab') {
			return;
		}

		var focusableElements = getFocusableElements();

		if (!focusableElements.length) {
			return;
		}

		var firstElement = focusableElements[0];
		var lastElement = focusableElements[focusableElements.length - 1];

		if (event.shiftKey && document.activeElement === firstElement) {
			event.preventDefault();
			lastElement.focus();
		} else if (!event.shiftKey && document.activeElement === lastElement) {
			event.preventDefault();
			firstElement.focus();
		}
	}

	function handleEscape(event) {
		if (event.key === 'Escape' && root.getAttribute('data-rx-open') === 'true') {
			closeMenu();
		}
	}

	function setupSubmenus() {
		var parents = root.querySelectorAll('.menu-item-has-children');

		Array.prototype.forEach.call(parents, function (parent, index) {
			var link = parent.querySelector(':scope > a');
			var submenu = parent.querySelector(':scope > .sub-menu');

			if (!link || !submenu) {
				return;
			}

			var button = document.createElement('button');
			var submenuId = submenu.getAttribute('id') || 'rx-mobile-submenu-' + index;

			submenu.setAttribute('id', submenuId);
			submenu.setAttribute('hidden', '');

			button.type = 'button';
			button.className = 'rx-mobile-menu__submenu-toggle';
			button.setAttribute('aria-expanded', 'false');
			button.setAttribute('aria-controls', submenuId);
			button.innerHTML = '<span class="screen-reader-text"><?php echo esc_js( __( 'Toggle submenu', 'rx-theme' ) ); ?></span><span aria-hidden="true">+</span>';

			link.insertAdjacentElement('afterend', button);

			button.addEventListener('click', function () {
				var expanded = button.getAttribute('aria-expanded') === 'true';

				button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
				parent.classList.toggle('rx-submenu-is-open', !expanded);

				if (expanded) {
					submenu.setAttribute('hidden', '');
					button.querySelector('[aria-hidden="true"]').textContent = '+';
				} else {
					submenu.removeAttribute('hidden');
					button.querySelector('[aria-hidden="true"]').textContent = '−';
				}
			});
		});
	}

	function setupThemeToggle() {
		if (!themeToggle) {
			return;
		}

		var storageKey = 'rx-theme-preference';

		function applyTheme(theme) {
			document.documentElement.setAttribute('data-rx-theme', theme);
			themeToggle.setAttribute('aria-pressed', theme === 'dark' ? 'true' : 'false');
		}

		var savedTheme = window.localStorage ? localStorage.getItem(storageKey) : null;

		if (savedTheme) {
			applyTheme(savedTheme);
		}

		themeToggle.addEventListener('click', function () {
			var currentTheme = document.documentElement.getAttribute('data-rx-theme') || 'light';
			var nextTheme = currentTheme === 'dark' ? 'light' : 'dark';

			applyTheme(nextTheme);

			if (window.localStorage) {
				localStorage.setItem(storageKey, nextTheme);
			}
		});
	}

	function setupBackToTop() {
		if (!backToTop) {
			return;
		}

		backToTop.addEventListener('click', function () {
			window.scrollTo({
				top: 0,
				behavior: 'smooth'
			});

			closeMenu();
		});
	}

	Array.prototype.forEach.call(openButtons, function (button) {
		button.addEventListener('click', openMenu);
	});

	Array.prototype.forEach.call(closeButtons, function (button) {
		button.addEventListener('click', closeMenu);
	});

	document.addEventListener('keydown', trapFocus);
	document.addEventListener('keydown', handleEscape);

	setupSubmenus();
	setupThemeToggle();
	setupBackToTop();
})();
</script>

Also add these menu locations in your inc/core/menus.php or functions.php:

register_nav_menus(
	array(
		'primary'          => esc_html__( 'Primary Menu', 'rx-theme' ),
		'mobile'           => esc_html__( 'Mobile Menu', 'rx-theme' ),
		'mobile-secondary' => esc_html__( 'Mobile Secondary Menu', 'rx-theme' ),
		'footer'           => esc_html__( 'Footer Menu', 'rx-theme' ),
	)
);

And call it inside header.php:

<?php get_template_part( 'template-parts/header/mobile-menu' ); ?>

For best performance later, move the inline <script> into:

assets/js/mobile-menu.js

Then enqueue it from your enqueue.php.

Leave a Reply

Your email address will not be published. Required fields are marked *