<!-- wexa.css + menu.css, no layout class: horizontal, sticky at top -->
<nav class="nav-wexa" aria-label="Main navigation">
<a class="menuitem" href="#">Home</a>
<a class="menuitem" href="#" aria-current="page">Current</a>
</nav>
Menus
wexa.css gives nav.nav-wexa its base styles: colors,
shadow, and a horizontal flex layout sticky at the top of the page.
menu.css extends this with fixed-position layout classes,
collapsible behavior, and off-canvas submenus.
Basic layouts require no JavaScript.
The companion MenuManager class adds the interactive behaviors:
pin, mobile toggle, and off-canvas submenu open/close.
Default nav — wexa.css
A nav.nav-wexa without any layout class is horizontal and sticky at
the top of the page. wexa.css applies nav colors
(--nav-bg-color, --nav-fg-color), shadow, and flex
layout. menu.css adds item sizing and min-height.
No layout class, no JavaScript required.
Variables
| Variable | Role |
|---|---|
--nav-bg-color |
Nav background color (solid color or gradient) |
--nav-fg-color |
Nav foreground color (text and icon) |
Layout classes
Add one class to nav.nav-wexa to fix its position on the page.
The body receives a matching padding automatically via
body:has(nav.nav-wexa.xxx) selectors.
No JavaScript required for static layouts.
.top— fixed horizontal bar at the top of the page..bottom— fixed horizontal bar at the bottom. Preferred for mobile applications..side— fixed vertical bar on the left, always expanded..side.collapsible— same, but collapses to icon-only width at rest. Expands on hover or keyboard focus.<span>labels are hidden when collapsed and shown when expanded.
Live demos: Side menu · Bottom + top menu
See live demos linked above.
<!-- Fixed top -->
<nav class="nav-wexa top" aria-label="Main navigation">...</nav>
<!-- Fixed bottom -->
<nav class="nav-wexa bottom" aria-label="Main navigation">...</nav>
<!-- Fixed left side, always expanded -->
<nav class="nav-wexa side" aria-label="Main navigation">...</nav>
<!-- Fixed left side, collapsible on hover/focus -->
<nav id="nav-content" class="nav-wexa side collapsible"
aria-label="Main navigation">
<a class="menuitem" href="#">
<svg aria-hidden="true">...</svg>
<span>Home</span>
</a>
</nav>
Header nav — #header-nav-content
The special ID header-nav-content places a compact icon-only nav at
the top-right of the header (absolute positioned, no shadow, no background).
Use it for small accessibility or branding controls inside a decorative header.
See the side menu demo header for a live example.
<header>
<nav id="header-nav-content" aria-label="Accessibility navigation">
<button class="menuitem" aria-label="..."></button>
</nav>
</header>
Variables
| Variable | Default | Role |
|---|---|---|
--nav-min-height |
max(calc(4rem / var(--size-divisor)), 3rem) |
Height of top/bottom nav bar |
--nav-min-width |
0 |
Collapsed width of side nav (set > 0 for side menus) |
--nav-expanded-width |
max(calc(16rem / var(--size-divisor)), 14rem) |
Side nav width when expanded or pinned |
--nav-item-width-v-collapsed |
var(--nav-min-width) |
Item width when side nav is collapsed |
--nav-item-width-v-expanded |
var(--nav-expanded-width) |
Item width when side nav is expanded |
--nav-item-height |
max(calc(2.5rem / var(--size-divisor)), 2.5rem) |
Default height of any menu item |
--menu-button-size |
max(calc(4rem / var(--size-divisor)), 3rem) |
Size of the #menu-button hamburger button |
Menuitems and inline submenus
Apply role="menuitem" or the .menuitem class to any
interactive element inside nav.nav-wexa. Both target identical styles.
Items typically combine an <svg> icon with a
<span> label. Use aria-current="page" to mark the
active item.
<nav class="nav-wexa ..." aria-label="Main navigation">
<a class="menuitem" href="#">
<svg aria-hidden="true">...</svg>
<span>Back</span>
</a>
<button class="menuitem" type="button">
<svg aria-hidden="true">...</svg>
<span>Help</span>
</button>
<a class="menuitem" href="#" aria-current="page">
<span>Current</span>
</a>
</nav>
Grouping with <section>
Items inside a side nav can be grouped in <section> elements.
Sections stack vertically with automatic spacing between them.
Exception: aside.appmenu must never be placed inside
a section — it must be a direct child of <nav>.
Inline submenus
For horizontal menus (.top, default), wrap items in
<ul><li> and add .submenu to the nested
<ul>. The submenu appears on hover or keyboard
:focus-within — no JavaScript required.
Add .expand to the parent item to show a ▼ indicator.
Background color helpers: .submenu-gray, .submenu-red,
.submenu-green, .submenu-orange.
Inline submenus only activate when the nav is a direct child of
<body> (CSS selector: body > nav > * *:hover .submenu).
<nav class="nav-wexa top" aria-label="Main navigation">
<a class="menuitem" href="#">Home</a>
<ul>
<li>
<a class="expand menuitem" href="#">Products</a>
<ul class="submenu submenu-gray">
<li><a role="menuitem" href="#">Item A</a></li>
<li><a role="menuitem" href="#">Item B</a></li>
</ul>
</li>
<li><a class="menuitem" href="#">About</a></li>
</ul>
</nav>
MenuManager — JavaScript
Requires JavaScript.
MenuManager handles three interactive behaviors that cannot be achieved
with CSS alone.
Mobile toggle — #menu-button
A fixed circular button visible only on screens narrower than 896 px.
Place it in <header>; wire it with
initMobileToggle(). Manages aria-expanded on the
button and .expanded on the side nav.
See live demo: Side menu
<!-- In <header> -->
<button id="menu-button" aria-controls="nav-content"
aria-expanded="false" aria-label="Toggle navigation menu">
<svg viewBox="0 0 24 24" ...>
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
Pin button — #pin-menu
Optional. Place inside the side nav. Wired by initSideMenu().
When activated, sets aria-pinned="true" on the nav and keeps it
expanded. Hidden on narrow screens (below 896 px).
See live demo: Side menu
<nav id="nav-content" class="nav-wexa side collapsible">
<button id="pin-menu" class="menuitem menu-pin"
type="button" aria-controls="nav-content"
aria-pressed="true" aria-label="Pin menu">
<svg aria-label="Pushpin">...</svg>
</button>
...
</nav>
Off-canvas panels — aside.appmenu
An aside.appmenu slides in from an edge when its toggle button
is activated. Registered with registerSubmenu(asideId, toggleId).
Position each panel with CSS variables on the aside element:
--appmenu-position (left | right |
top | bottom) and
--appmenu-align (two keywords: horizontal then vertical,
e.g. left top).
MenuManager reads these and sets the corresponding
data-submenu-position and data-submenu-align-*
attributes, which the CSS then targets.
The aside must be a direct child of <nav>,
never wrapped in a <section>: background: inherit
would otherwise inherit the section's transparent background instead of the nav's.
Use .nav-section-label on a <p> inside the
appmenu nav to display a non-interactive section heading.
Live demos: Side menu · Bottom menu
<nav id="nav-content" class="nav-wexa side collapsible">
<button class="menuitem" id="toggle-settings"
aria-haspopup="true" aria-controls="appmenu-settings">
<svg aria-hidden="true">...</svg>
<span>Settings</span>
</button>
<!-- Direct child of nav — never inside <section> -->
<aside id="appmenu-settings" class="appmenu"
style="--appmenu-position: left; --appmenu-align: left top;"
aria-label="Settings submenu">
<nav>
<p class="nav-section-label">Settings</p>
<a class="menuitem" href="#">Profile</a>
<a class="menuitem" href="#">Preferences</a>
</nav>
</aside>
</nav>
const menu = new MenuManager();
menu.initSideMenu();
menu.initMobileToggle();
menu.registerSubmenu('appmenu-settings', 'toggle-settings');
Variables — appmenu
| Variable | Default | Role |
|---|---|---|
--appmenu-position |
left |
Edge from which the panel slides in (left | right | top | bottom) |
--appmenu-align |
left top |
Inner alignment of items (horizontal keyword then vertical keyword) |