Novus Technologies Novus Technologies design kit v0.9.0
Menu

Navigation

Top navigation

The second console shell: sections in a bar under the header, menus on click, native so it works without JavaScript, with the drawer taking over on phones.

.topnav · .topnav__inner · .topnav__link · .topnav__item · .topnav__trigger · .topnav__menu

The second console shell. Sections live in a bar under the header and their menus open on click. The side navigation remains the default; this suits a console with a handful of sections rather than a deep one.

Top navigation: sections in the bar, menus on click
<nav class="topnav" aria-label="Sections">
  <div class="topnav__inner">
    <a class="topnav__link" href="#" aria-current="page">Dashboard</a>
    <a class="topnav__link" href="#">Analytics</a>
    <details class="topnav__item" data-dismiss>
      <summary class="topnav__trigger">Operations<svg class="icon icon--sm topnav__chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></summary>
      <div class="card topnav__menu">
        <a class="navlink" href="#">Transactions</a>
        <a class="navlink" href="#">Data grid</a>
        <a class="navlink" href="#">Terminals</a>
      </div>
    </details>
  </div>
</nav>

How it behaves

  • Click, never hover. Each section is a native disclosure element, so the menus open with JavaScript off. The shared console script closes an open menu on an outside press and on Escape, and returns focus to the section that opened it.
  • One menu at a time when scripting is available. Without it, more than one can be open, and the layout tolerates that.
  • The current section stays marked while its menu is shut, so an operator can always see where they are.
  • Nothing is hidden. Every destination stays reachable at every width, and a release gate compares what the bar can reach against the drawer, which is rendered from the same definition.
  • Phones use the drawer. Below 900px the bar steps aside and the drawer from the side-navigation shell takes over, so there is one phone navigation in the kit.

Markup

A console opts in with adminwrap--topnav, which hides the side column at desktop width. The strip sits between the header and the console body, and the drawer markup stays in place for phone widths. The header above the strip is the same as in the side-navigation shell: the app switcher comes first, then the menu toggle and the product lockup. The top-navigation demo shows the whole shell with it.

Which shell to choose

  • Use the side navigation when a console has many top-level areas. A rail shows them all at once, and a bar would push most of them behind menus.
  • Use the top navigation when a console has a handful of sections, up to about nine, each holding a short menu, and the horizontal bar reads as the product's spine.
  • Both shells use the same navigation definition, the same header with the account menu last, and the same drawer on phones, so moving between them is a shell change rather than a rebuild.

CSS

These rules ship in the console pattern layer, admin-kits/shared/novus-admin.css, which this site and every Admin Kit flavor load after tokens.css. Copy the whole layer, or only the sections below; every value is a token.

@pattern top-navigation
/* The second console shell (feature 012): primary navigation in a bar under the
   header, with menus that open on CLICK, never on hover. Built on native details
   so it works when scripting does not run, like every other dismissable surface
   here. Below the desktop breakpoint this bar steps aside and the drawer from the
   side-navigation shell takes over, so there is one phone navigation in the kit.
   The side-navigation shell remains the default; see the catalog for which to
   choose. */
.topnav { position: relative; z-index: 50; background: var(--surface); border-bottom: 1px solid var(--border); }
/* No scroll container here. A menu is absolutely positioned inside this strip,
   and an ancestor that scrolls clips it: overflow-x: auto forces overflow-y to
   auto as well, so the dropdown would be cut off at the bar's own height. The
   bar wraps instead, which keeps every section reachable AND visible. */
.topnav__inner { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--space-1); max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
.topnav__link, .topnav__trigger { display: inline-flex; align-items: center; gap: var(--space-2); min-height: 2.75rem; padding: var(--space-2) var(--space-3); color: var(--text-secondary); font-size: var(--text-sm); font-weight: var(--weight-regular); white-space: nowrap; text-decoration: none; border-bottom: 2px solid transparent; cursor: pointer; transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease; }
.topnav__link:hover, .topnav__trigger:hover { color: var(--text); background: var(--bg-subtle); text-decoration: none; }
.topnav__link:focus-visible, .topnav__trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The current section stays marked while its menu is closed. */
.topnav__link[aria-current="page"], .topnav__item.is-current > .topnav__trigger { color: var(--accent-text); border-bottom-color: var(--accent); font-weight: var(--weight-bold); }
.topnav__item { position: relative; display: flex; }
.topnav__trigger { list-style: none; }
.topnav__trigger::-webkit-details-marker { display: none; }
.topnav__chevron { transition: transform 0.15s ease; }
.topnav__item[open] > .topnav__trigger .topnav__chevron { transform: rotate(180deg); }
.topnav__menu { position: absolute; inset-inline-start: 0; top: 100%; z-index: 60; min-width: 14rem; max-height: min(24rem, calc(100vh - 8rem)); overflow-y: auto; padding: var(--space-2); display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--text-sm); line-height: var(--lh-sm); }
.topnav__menu .navlink { border-inline-start: 0; border-radius: var(--radius-md); }
/* A console opts into this shell with .adminwrap--topnav. Above the breakpoint the
   strip is the navigation and the side column steps aside; below it the strip hides
   and the drawer from the side-navigation shell takes over, unchanged. */
.adminwrap--topnav .adminbody { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 900px) {
  .adminwrap--topnav .adminnav { display: none; }
  .adminwrap--topnav .navtoggle { display: none; }
}
@media (max-width: 899.98px) { .topnav { display: none; } }