- You’ll build
- Responsive Navigation Menu in WordPress
- Code required
- HTML + CSS + JS
- Installation
- Theme and role dependent
On this page
Before you startHow the responsive menu worksInstall the menu in WordPressCustomize labels and destinationsAdapt the layout carefullyTroubleshoot navigation issuesAccessibility and performanceFrequently asked questionsCheck your installationComplete working code
The preview and download are generated from these same source files. The complete snippet requires permission to save inline styles and scripts.
<nav class="iljs-demo iljs-nav" aria-label="Example primary navigation"><a class="iljs-nav-brand" href="#iljs-home">Studio.</a><button type="button" aria-expanded="false" aria-controls="iljs-nav-links">Menu</button><ul id="iljs-nav-links"><li><a href="#iljs-home">Home</a></li><li><a href="#iljs-work">Our work</a></li><li><a href="#iljs-contact">Contact</a></li></ul></nav>
/* Change the values here, keeping the iljs- prefix to avoid theme conflicts. */
.iljs-demo { --iljs-accent:#9235f6; --iljs-radius:18px; font-family:system-ui,sans-serif; color:#242038; width:min(620px,100%); margin:auto; }
.iljs-demo button,.iljs-demo a { font:inherit; }
.iljs-demo button:focus-visible,.iljs-demo a:focus-visible,.iljs-demo input:focus-visible,.iljs-demo summary:focus-visible { outline:3px solid #c491ff; outline-offset:4px; }
.iljs-nav { display:flex;align-items:center;justify-content:space-between;gap:20px;background:#fff;border:1px solid #e8dff1;border-radius:12px;padding:18px 23px;box-sizing:border-box; }
.iljs-nav-brand { font-size:23px;font-weight:700;text-decoration:none;color:var(--iljs-accent); }
.iljs-nav ul { display:flex;gap:22px;list-style:none;margin:0;padding:0; }
.iljs-nav ul a { color:#57465f;text-decoration:none;font-size:13px; }
.iljs-nav button { display:none;background:#f5eaff;color:#7b2ec5;border:1px solid #d9b7fa;border-radius:6px;padding:8px 12px;cursor:pointer; }
@media(max-width:600px){.iljs-nav{flex-wrap:wrap}.iljs-nav button{display:block}.iljs-nav ul{display:flex;flex-basis:100%;flex-direction:column;padding:15px 0}.iljs-nav[data-iljs-ready] ul{display:none}.iljs-nav[data-iljs-ready][data-open="true"] ul{display:flex}}
(() => {
const init=()=>document.querySelectorAll('.iljs-nav').forEach(root=>{
if(root.dataset.iljsReady)return;root.dataset.iljsReady='true';const toggle=root.querySelector('button');const links=root.querySelector('ul');
const setOpen=open=>{root.dataset.open=String(open);toggle.setAttribute('aria-expanded',String(open));};
toggle.addEventListener('click',()=>setOpen(root.dataset.open!=='true'));
root.addEventListener('keydown',event=>{if(event.key==='Escape'&&root.dataset.open==='true'){setOpen(false);toggle.focus();}});
links.addEventListener('click',event=>{if(event.target.closest('a'))setOpen(false);});
document.addEventListener('click',event=>{if(!root.contains(event.target))setOpen(false);});
const mq=window.matchMedia('(min-width:601px)');mq.addEventListener('change',()=>setOpen(false));
});document.readyState==='loading'?document.addEventListener('DOMContentLoaded',init,{once:true}):init();
})();
<nav class="iljs-demo iljs-nav" aria-label="Example primary navigation"><a class="iljs-nav-brand" href="#iljs-home">Studio.</a><button type="button" aria-expanded="false" aria-controls="iljs-nav-links">Menu</button><ul id="iljs-nav-links"><li><a href="#iljs-home">Home</a></li><li><a href="#iljs-work">Our work</a></li><li><a href="#iljs-contact">Contact</a></li></ul></nav>
<style>
/* Change the values here, keeping the iljs- prefix to avoid theme conflicts. */
.iljs-demo { --iljs-accent:#9235f6; --iljs-radius:18px; font-family:system-ui,sans-serif; color:#242038; width:min(620px,100%); margin:auto; }
.iljs-demo button,.iljs-demo a { font:inherit; }
.iljs-demo button:focus-visible,.iljs-demo a:focus-visible,.iljs-demo input:focus-visible,.iljs-demo summary:focus-visible { outline:3px solid #c491ff; outline-offset:4px; }
.iljs-nav { display:flex;align-items:center;justify-content:space-between;gap:20px;background:#fff;border:1px solid #e8dff1;border-radius:12px;padding:18px 23px;box-sizing:border-box; }
.iljs-nav-brand { font-size:23px;font-weight:700;text-decoration:none;color:var(--iljs-accent); }
.iljs-nav ul { display:flex;gap:22px;list-style:none;margin:0;padding:0; }
.iljs-nav ul a { color:#57465f;text-decoration:none;font-size:13px; }
.iljs-nav button { display:none;background:#f5eaff;color:#7b2ec5;border:1px solid #d9b7fa;border-radius:6px;padding:8px 12px;cursor:pointer; }
@media(max-width:600px){.iljs-nav{flex-wrap:wrap}.iljs-nav button{display:block}.iljs-nav ul{display:flex;flex-basis:100%;flex-direction:column;padding:15px 0}.iljs-nav[data-iljs-ready] ul{display:none}.iljs-nav[data-iljs-ready][data-open="true"] ul{display:flex}}
</style>
<script>
(() => {
const init=()=>document.querySelectorAll('.iljs-nav').forEach(root=>{
if(root.dataset.iljsReady)return;root.dataset.iljsReady='true';const toggle=root.querySelector('button');const links=root.querySelector('ul');
const setOpen=open=>{root.dataset.open=String(open);toggle.setAttribute('aria-expanded',String(open));};
toggle.addEventListener('click',()=>setOpen(root.dataset.open!=='true'));
root.addEventListener('keydown',event=>{if(event.key==='Escape'&&root.dataset.open==='true'){setOpen(false);toggle.focus();}});
links.addEventListener('click',event=>{if(event.target.closest('a'))setOpen(false);});
document.addEventListener('click',event=>{if(!root.contains(event.target))setOpen(false);});
const mq=window.matchMedia('(min-width:601px)');mq.addEventListener('change',()=>setOpen(false));
});document.readyState==='loading'?document.addEventListener('DOMContentLoaded',init,{once:true}):init();
})();
</script>Before you start
The supplied Home, Our work, and Contact links point to actual sections in the standalone demo. Replace them with your own page URLs in WordPress. A static list does not automatically read a menu you configured in the WordPress dashboard. If you need a complete header installation path, start with the glassmorphism header tutorial.
How the responsive menu works
The .iljs-nav root is a semantic navigation landmark. A brand link, a toggle button, and a list of links live inside it. The toggle’s aria-controls matches the list’s ID, while aria-expanded records the current disclosure state. The desktop layout uses flex alignment with ordinary links.
Below 601 pixels, the root wraps and the links become a vertical list. Before JavaScript initializes, that list stays visible. Once the script sets data-iljs-ready, CSS hides it until data-open="true". That is a deliberate progressive enhancement: if scripting fails, mobile visitors still have navigation rather than a permanently hidden menu.
JavaScript scopes its queries to each root, guards initialization, and updates the state attribute and accessible state together. Escape closes an open menu and returns focus to the toggle. Activating a link or clicking outside closes it. A viewport change across the desktop breakpoint resets the disclosure so a previously open mobile state does not unexpectedly reappear.
Install the menu in WordPress
- Add the fragment to a Custom HTML block in a draft page to test the component locally. It can serve as page-level navigation or a section menu.
- Replace all three sample links and the brand destination with actual URLs. Avoid leaving
#iljs-workon a page that has no matching section. - Install the scoped CSS in your available custom CSS interface or a stylesheet. Use the CSS setup guide when the screen differs by theme.
- Install the JavaScript through a permitted method. Dedicated Custom HTML JavaScript panels in WordPress 7.0 depend on
unfiltered_html; restricted accounts may need an administrator-approved file installation. - Preview the front end at desktop and mobile widths. Open the menu, tab through its links, press Escape, and verify the destinations.
For a site-wide block-theme header, edit the actual Header template part rather than pasting this menu into a post. The block-theme replacement guide explains the distinction. Classic themes need a supported hook or child-theme template adaptation. Elementor requires a genuine header template and applicable Theme Builder access to replace global navigation; an HTML widget in one page only affects that page.
Customize labels and destinations
Use short link labels that make sense without the surrounding design. “Our work” is suitable when it actually reaches a portfolio; a product site might use Products instead. The root’s aria-label should describe this landmark. If it is secondary navigation within a page, call it something such as “Service sections” rather than claiming it is the primary site navigation.
The .iljs-nav-brand color uses --iljs-accent. Change the logo text or replace it with an image that has appropriate alternative text and reserved dimensions. Keep the anchor’s accessible name meaningful after changing the visual mark. The supplied toggle says Menu; change it only if the new wording describes the action clearly.
If you add several menu instances to one page, give each link list a distinct ID and update its toggle’s aria-controls accordingly. The script’s scoped queries prevent most cross-instance confusion, but duplicate IDs still create invalid relationships for assistive technology. Our class and ID guide explains the difference between repeatable classes and unique identifiers.
Adapt the layout carefully
The mobile breakpoint is 600 pixels. Change both the CSS breakpoint and the JavaScript matchMedia query together if you need another threshold. A longer set of links may require mobile layout sooner than a short three-link menu. Test the longest real labels, not just the short sample text.
This disclosure expands in the document; it does not cover the screen or trap keyboard focus. That keeps behavior straightforward. Do not add a dialog focus trap to this ordinary menu. If you create a fullscreen overlay, its focus, dismissal, background interaction, and scroll behavior become a different implementation problem.
The luxury static header shows how a similar accessible disclosure can fit into a deliberately restrained global header design. Its normal-flow positioning also avoids many sticky stacking complications.
Troubleshoot navigation issues
The toggle opens nothing. Check that WordPress saved the script and that the root still has .iljs-nav. The button and list must remain in the same component root.
The menu is always visible on phones. If the script did not initialize, this is the intentional fallback. Install the script to enable the disclosure. If it did initialize, inspect the data-iljs-ready and data-open attributes and the breakpoint rules.
Links stay in the tab order when hidden. The supplied mobile CSS uses display: none, which removes the closed list from navigation. A customization that only changes opacity would leave invisible focusable links; restore a real hidden state.
A link scrolls nowhere. Replace the demo anchor with a real destination or add the matching section ID. An anchor beginning with # is only useful when that element exists on the current page.
The header appears twice. You installed a page-level menu without replacing the theme’s header. Decide whether this is intentional section navigation or follow a real global replacement path.
Accessibility and performance
The semantic navigation and list provide understandable structure. The toggle is a real button, and the links remain anchors. Escape returns focus only when it closes an open menu, rather than moving focus unexpectedly during unrelated typing. Outside-click closing does not interfere with keyboard operation.
There is no animation loop, network request, or large menu library. The layout changes with CSS, while JavaScript manages only the disclosure state. Without scripts, the links remain visible. Test 320-pixel widths, larger text, long labels, and the transition back to desktop.
Frequently asked questions
Will it use my WordPress menu automatically?
No. These are static links. A dynamic menu needs a WordPress Navigation block or an appropriate theme integration such as wp_nav_menu, with matching styling and behavior.
Can I use it as a page section menu?
Yes. Set a descriptive landmark label and point each link to a real section ID. This does not require replacing the site’s primary navigation.
Does it need a focus trap?
No. This is an in-flow disclosure, not a modal dialog. Visitors should be able to continue tabbing into the rest of the page.
Check your installation
Confirm all links resolve, a closed mobile menu has no hidden keyboard stops, and Escape returns focus to the toggle. Then check that the menu’s location matches your intended page-level or global use.


