COMPONENTS / NAVIGATION

Top menu

Responsive site navigation using native HTML popovers and CSS. Desktop and mobile lists share the same links, with nested submenus at any depth. Clicking outside dismisses open menus without JavaScript.

Responsive menu with nested dropdowns

Open Products, Frameworks and Components. Click outside to close the stack, or press Escape to close the deepest panel. On mobile, Back returns to the parent panel.

HTMLSelect text to copy
<nav class="top-menu" aria-label="Example navigation" style="--re-menu-anchor: --documentation-navigation">
  <a class="menu-brand" href="#main">RevaCSS</a>
  <ul class="menu-items menu-desktop">
    <li>
      <a href="#main" aria-current="page">Home</a>
    </li>
    <li class="menu-dropdown" style="--re-menu-branch-anchor: --documentation-desktop-products-anchor">
      <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products">Products</button>
      <ul class="menu-panel" id="documentation-desktop-products" popover="auto">
        <li class="menu-back">
          <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products" popovertargetaction="hide">Back to main menu</button>
        </li>
        <li>
          <a href="#main">All products</a>
        </li>
        <li class="menu-dropdown">
          <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products-frameworks">Frameworks</button>
          <ul class="menu-panel" id="documentation-desktop-products-frameworks" popover="auto">
            <li class="menu-back">
              <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products-frameworks" popovertargetaction="hide">Back to Products</button>
            </li>
            <li>
              <a href="#main">CSS foundation</a>
            </li>
            <li class="menu-dropdown">
              <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products-frameworks-components">Components</button>
              <ul class="menu-panel" id="documentation-desktop-products-frameworks-components" popover="auto">
                <li class="menu-back">
                  <button type="button" class="menu-toggle" popovertarget="documentation-desktop-products-frameworks-components" popovertargetaction="hide">Back to Frameworks</button>
                </li>
                <li>
                  <a href="#main">Top menu</a>
                </li>
                <li>
                  <a href="#main">Forms and controls</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#main">Templates</a>
        </li>
      </ul>
    </li>
    <li class="menu-dropdown" style="--re-menu-branch-anchor: --documentation-desktop-resources-anchor">
      <button type="button" class="menu-toggle" popovertarget="documentation-desktop-resources">Resources</button>
      <ul class="menu-panel" id="documentation-desktop-resources" popover="auto">
        <li class="menu-back">
          <button type="button" class="menu-toggle" popovertarget="documentation-desktop-resources" popovertargetaction="hide">Back to main menu</button>
        </li>
        <li>
          <a href="#main">Get started</a>
        </li>
        <li>
          <a href="#main">API reference</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#main">Contact</a>
    </li>
  </ul>
  <button type="button" class="menu-toggle menu-collapse" popovertarget="documentation-mobile-menu" aria-label="Menu"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg></button>
  <ul class="menu-items menu-mobile" id="documentation-mobile-menu" popover="auto">
    <li class="menu-back">
      <button type="button" class="menu-toggle" popovertarget="documentation-mobile-menu" popovertargetaction="hide">Close menu</button>
    </li>
    <li>
      <a href="#main" aria-current="page">Home</a>
    </li>
    <li class="menu-dropdown">
      <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products">Products</button>
      <ul class="menu-panel" id="documentation-mobile-products" popover="auto">
        <li class="menu-back">
          <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products" popovertargetaction="hide">Back to main menu</button>
        </li>
        <li>
          <a href="#main">All products</a>
        </li>
        <li class="menu-dropdown">
          <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products-frameworks">Frameworks</button>
          <ul class="menu-panel" id="documentation-mobile-products-frameworks" popover="auto">
            <li class="menu-back">
              <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products-frameworks" popovertargetaction="hide">Back to Products</button>
            </li>
            <li>
              <a href="#main">CSS foundation</a>
            </li>
            <li class="menu-dropdown">
              <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products-frameworks-components">Components</button>
              <ul class="menu-panel" id="documentation-mobile-products-frameworks-components" popover="auto">
                <li class="menu-back">
                  <button type="button" class="menu-toggle" popovertarget="documentation-mobile-products-frameworks-components" popovertargetaction="hide">Back to Frameworks</button>
                </li>
                <li>
                  <a href="#main">Top menu</a>
                </li>
                <li>
                  <a href="#main">Forms and controls</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#main">Templates</a>
        </li>
      </ul>
    </li>
    <li class="menu-dropdown">
      <button type="button" class="menu-toggle" popovertarget="documentation-mobile-resources">Resources</button>
      <ul class="menu-panel" id="documentation-mobile-resources" popover="auto">
        <li class="menu-back">
          <button type="button" class="menu-toggle" popovertarget="documentation-mobile-resources" popovertargetaction="hide">Back to main menu</button>
        </li>
        <li>
          <a href="#main">Get started</a>
        </li>
        <li>
          <a href="#main">API reference</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#main">Contact</a>
    </li>
  </ul>
</nav>

Component API summary · Optional glass material

Set data-navbar on html, a parent section, or the navigation itself. It inherits through the page; a local data-navbar="standard" resets a parent choice. It only affects .top-menu, leaving sidebars unchanged.

ValueLayout
standardDefault: brand left, links and actions right.
centeredBalanced desktop grid: brand left, links in the middle, actions right.
floatingStandard alignment in an inset surface with a 1rem outer margin and the shared page width limit. Combine it with data-navbar-position to choose normal or sticky scrolling.
splitCentral brand with the primary link group on the left and an optional second group or actions on the right.

Split can use a second .menu-items.menu-desktop list inside .menu-actions. Combine both groups in the single mobile list. Each desktop list keeps its own hover glide and dropdown anchors. Group additional actions in .menu-actions to avoid overlapping grid cells. Desktop links wrap inside their allocated column; below 48rem every preset retains the existing Menu button and native mobile popovers. Split changes visual placement, so choose a logical DOM and keyboard order for your content rather than expecting CSS to reorder focus.

<html lang="en" data-navbar="centered">
  <!-- All top menus inherit centered layout. -->
  <nav class="top-menu" data-navbar="standard" aria-label="Local navigation">
    <!-- This menu overrides the parent layout. -->
  </nav>
</html>

Set data-navbar-position="sticky" on html or the page's scroll region. The default static keeps normal flow. Position is independent of all four data-navbar layouts, palette and material.

Wrap a menu in header.navbar-header when it sits inside a header. Put that wrapper beside the page content, outside short sections. The wrapper stays visible while its inner menu remains the positioning anchor for dropdowns. A standalone .top-menu or semantic .app-shell > header can also inherit the setting.

A sticky page header

CSS owns the sticky behaviour. Use static on the same outer wrapper to reset an inherited sticky choice.

HTMLSelect text to copy
<html lang="en" data-navbar="standard" data-navbar-position="sticky">
  <body>
    <header class="navbar-header container">
      <nav class="top-menu" aria-label="Main navigation">
        <a class="menu-brand" href="#home">Studio</a>
        <!-- Use the complete responsive menu markup above. -->
      </nav>
    </header>
    <main id="home">
      <!-- Page content gives the header room to remain sticky. -->
    </main>
  </body>
</html>

Sticky headers stay within their containing block and follow the nearest scroll container. An ancestor with overflow:auto, scroll or hidden changes that container; avoid accidental overflow wrappers. Native popovers and dialogs remain in the browser's top layer.

--re-navbar-offset adds space above the bar; safe-area space is added separately. --re-navbar-height reserves room for anchor destinations and keyboard focus. Its CSS-only fallback is 6rem plus Floating margins. Set it to the complete header height when your branding or links wrap. --re-anchor-offset retains the small gap below the header. --re-navbar-z controls the sticky stacking level; the default is 30.

The documentation and standalone demos start sticky. Their optional settings helper measures actual header height after font loading or layout changes. Demo pages use their own navigation with a shared Customize control and a Docs link. Custom applications can set the height in ordinary CSS; the RevaCSS core requires no script or scroll listener. Print hides navigation headers.

Try the complete marketing demo · Try the dashboard

Position, spacing and width are independent. The framework defaults to spaced and contained; the documentation and complete demos start flush and full.

AttributeValuesEffect
data-navbar-spacingflush, spacedFlush removes wrapper padding, Floating block margins and the top border, and suppresses surface shadows, highlights and gradients. On mobile it squares every corner; on desktop it squares the top corners. Spaced restores the material finish, adds wrapper breathing room and retains Floating margins; the wrapper gap is reduced below 48rem. Full desktop width always uses square corners.
data-navbar-widthcontained, fullContained follows the shared page width and the layout's own inset. Full fills the parent with a navbar surface while keeping its contents aligned to the shared page width. At 48rem and wider it squares every corner; on mobile the corners follow the spacing setting.

Flush header with a full-width surface

HTMLSelect text to copy
<html lang="en" data-navbar-position="sticky"
      data-navbar-spacing="flush" data-navbar-width="full">
  <body>
    <header class="navbar-header">
      <nav class="top-menu" aria-label="Main navigation">
        <!-- Logo, responsive links and actions. -->
      </nav>
    </header>
    <main class="container">
      <!-- Page content. -->
    </main>
  </body>
</html>

Keep the outer wrapper in a page-wide parent for a surface that reaches both screen edges. Full width takes precedence over the Floating width while retaining its alignment; Flush removes its block margin. App-shell toolbars keep their existing grid column. data-width still chooses the shared content width, and --re-navbar-space customizes the spaced wrapper gap. Avoid extra application padding on the header if it should meet the safe top edge.

The same customizer is used throughout this site. Documentation and demo pages share saved choices; demo pages include one demo navbar instead of an additional documentation navbar. Mobile headers place the compact Customize control before Menu, and the Docs link is available inside Customize. Demo calls to action remain available through their mobile navigation. Reset clears the shared profile, and HTML downloads include your current selections.

Use the markup

Copy the example above and replace its link destinations. Keep both lists in sync. Give each navigation a unique --re-menu-anchor CSS identifier for mobile positioning as shown in the example. Give each desktop dropdown list item a unique --re-menu-branch-anchor; its descendants inherit that identifier so nested panels remain beneath the visible top-level button. Every panel needs a unique document-wide id, and each toggle's popovertarget must match that id. Generate both layouts from a shared template. Nest child panels and their toggle buttons inside the parent panel so native dismissal preserves the hierarchy.

Structure and configuration

Class or settingUse
.top-menuNavigation landmark and containing surface.
data-navbarInherited layout: standard (default), centered, floating, split.
data-navbar-spacingInherited wrapper gap: spaced (default) or flush.
data-navbar-widthInherited frame and surface width: contained (default) or full.
.menu-actionsOptional right-hand group for actions and a second desktop list.
.menu-brandOptional brand link.
.menu-desktopDesktop list, visible at 48rem and wider.
.menu-collapseMobile Menu button, with class menu-toggle and popovertarget.
.menu-items.menu-mobileMobile top-level list with a unique id and popover auto.
.menu-dropdownList item containing a toggle button and its submenu.
.menu-toggleNative type button control using popovertarget to toggle a panel.
.menu-panelSubmenu list with a unique id and popover auto.
.menu-backList item containing a button with popovertargetaction hide, targeting its own panel.
popover="auto"Native outside-click and Escape dismissal. Another unrelated auto popover closes the current stack; descendants can remain open with their ancestors.
aria-current="page"Marks the current page link.
--re-menu-branch-anchorUnique CSS anchor identifier on each desktop dropdown list item; anchors that branch and its nested panels to the top-level toggle. Single panels can use the native implicit button anchor.
--re-menu-widthDesktop panel width, default 20rem, bounded by the viewport.

Responsive behavior

Below 48rem the Menu button opens a viewport-bounded panel. Nested panels replace the parent view; Back returns one level and Close menu dismisses the mobile stack. At 48rem and wider, CSS anchor positioning places each panel below its own top-level toggle button, with alternate positions near viewport edges. Nested panels replace the parent view on desktop too, keeping deep menus within one readable surface. Without CSS anchor positioning, panels remain usable as centered popovers.

The browser paints popovers in its top layer, so ancestor overflow does not clip them. The page remains scrollable and interactive. CSS hides panels belonging to the inactive layout when crossing the breakpoint; returning to that layout may restore its open state. Clicking outside or closing a parent dismisses its descendant panels.

Keyboard and accessibility

Tab and Shift+Tab move between visible links and buttons. Enter and Space activate toggle buttons. Escape closes the deepest open panel. Focus restoration follows the browser's native behavior; nested panels may not return focus to their opener. Use the visible Back controls to return through the hierarchy. Back and Close controls do not use autofocus, so opening by touch does not force a focus outline onto the first item. Tab moves into the browser's native popover focus order; keyboard focus indicators remain visible. The browser provides the toggle's expanded relationship through popovertarget; do not hard-code aria-expanded. Closed panel links leave the focus order. Use native navigation and list semantics, without application-menu roles or arrow-key expectations.

An outside click dismisses panels outside the clicked branch. Clicking a parent panel closes its deeper descendants while leaving that parent open. Clicking another unrelated menu closes the previous stack. An ordinary destination link inside a panel does not automatically hide it; a full page navigation replaces the document, while an in-page link can leave it open. Native popover support is required for this markup. Screen-reader and actual-device review remain release checks.

Gliding navigation highlight

The desktop top-level menu uses a shared rounded background that glides between hovered or visibly focused items, then returns to its current-page link when present. Nested and mobile lists use ordinary hover and focus styling without a gliding underline. Separate desktop menus keep independent highlight scopes. This default enhancement uses CSS anchor positioning and anchor-scope; ordinary hover, focus and current-page styling remain the fallback. When there is no current-page item, the first hover or keyboard focus displays the highlight directly at that item; moving between adjacent items glides it to the next. Parent data-motion controls duration, while reduced-motion preferences disable the glide. No JavaScript or extra class is required. The effect reserves ::after on the desktop menu-items list; it preserves the separate desktop branch anchors used for dropdown positioning.

Styling and modules

Shared theme, tone, density, shape, depth, border and control size settings continue to inherit. The optional glass stylesheet treats the panels as glass surfaces while navigation toggle buttons retain their neutral navigation appearance. Complete, scoped, component and dedicated navigation builds include the styles. Load reva.navigation.css after tokens and base modules; use the complete build for all accessibility utilities. Open panels and navigation are hidden in print.