Navbar
Sticky top bar chrome
Intent
Navbar is the sticky top bar of a site or app shell: logo on
one side, nav in the middle, actions on the other. It owns the
chrome -- surface, border, padding, stickiness, z-index -- and
nothing else. Interior layout comes from composition
(.cluster,
.center) and
link/button atoms (nav-link,
button).
An optional .subnavbar sticks just below the
navbar -- a breadcrumb trail, a section tab strip, a filter
bar. It offsets by --navbar-height so the two
bars stack cleanly when both are visible.
Semantic structure
.navbar is chrome -- the sticky strip, the
surface, the border. The semantic markup that wraps it is the
consumer's responsibility, and getting it right matters for
screen-reader landmark navigation, keyboard users, and the
document outline. The recommended scaffold:
<header class="navbar">
<div class="cluster is-between">
<a href="/"><strong>Brand</strong></a>
<nav class="cluster" aria-label="Primary">
<a class="nav-link" aria-current="page">Home</a>
<a class="nav-link">Products</a>
</nav>
<div class="cluster">
<button class="button">Sign in</button>
</div>
</div>
</header>
Three landmark / role decisions live in that scaffold:
-
<header>is the wrapper. It exposes the banner landmark when it's a top-level child of<body>-- screen- reader users can jump to it via landmark navigation. Don't add aroleattribute; the implicit landmark already works. -
Inner
<nav>witharia-label. The actual navigation links go inside a<nav>element, not directly on the<header>. The navigation landmark is for the nav region; the banner landmark is for the bar as a whole.aria-labeldistinguishes multiple<nav>elements on the page (primary nav, footer nav, sidebar nav, breadcrumb) -- screen readers announce each by its label, so they need to differ. -
Brand and actions stay outside the inner
<nav>. A brand link is a logo, not navigation in the screen-reader sense; action buttons (sign in, account menu, search) are controls, not navigation. Keeping them outside the<nav>keeps the navigation landmark focused on real nav links, which is what users expect when they jump to it.
Subnavbar semantic choices
.subnavbar is generic chrome -- the right
semantic element depends on what's inside it. Pick to match:
-
Section nav / tab strip:
<nav class="subnavbar" aria-label="Section">. A second navigation landmark, distinguished from the primary nav by its label. -
Breadcrumb:
<nav class="subnavbar" aria-label="Breadcrumb">with an<ol>inside. The ordered list signals the hierarchy; the label tells the screen reader it's a breadcrumb. -
Filter / sort / view controls (toolbar):
<div class="subnavbar" role="toolbar">. The toolbar role tells screen readers to navigate the bar with arrow keys, matching the pattern for command strips in native apps. A dedicated.toolbarcomponent with full keyboard semantics is on the roadmap; for now, the role +.subnavbarchrome is the right shape.
Don't repeat the primary nav inside the subnavbar -- duplicate
landmarks make screen-reader navigation harder, not easier. If
the secondary content has no obvious semantic wrapper, a plain
<div class="subnavbar"> is fine.
Basic usage
.navbar on a <header> wraps
whatever composition matches the shell. The canonical shape
uses .cluster.is-between to push three sections
(logo, nav, actions) apart; the <nav>
landmark surrounds only the actual navigation links.
<docs-resize>
<header class="navbar">
<div class="cluster is-between">
<a href="#"><strong>Acme</strong></a>
<nav class="cluster" aria-label="Primary">
<a href="#" class="nav-link" aria-current="page">Docs</a>
<a href="#" class="nav-link">Components</a>
<a href="#" class="nav-link">Blog</a>
</nav>
<div class="cluster">
<button class="button is-ghost" type="button">Sign in</button>
<button class="button" type="button">Sign up</button>
</div>
</div>
</header>
</docs-resize>With a subnavbar
.subnavbar is a second sticky bar that offsets
below the navbar. It reads --navbar-height
(falling back to 3rem when unset) so it knows
where to stick. Set the token on the navbar instance (or on
:root) to match the actual rendered height -- the
default covers a single-line navbar with snug padding, but a
taller navbar (larger logo, extra vertical padding) needs the
token set explicitly or the subnavbar sticks too high and
overlaps.
<docs-resize>
<header class="navbar">
<div class="cluster is-between">
<a href="#"><strong>Acme</strong></a>
<nav class="cluster" aria-label="Primary">
<a href="#" class="nav-link" aria-current="page">Docs</a>
<a href="#" class="nav-link">Components</a>
</nav>
</div>
</header>
<nav class="subnavbar" aria-label="Section">
<div class="breadcrumb">
<ol>
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><span aria-current="page">Widget</span></li>
</ol>
</div>
</nav>
</docs-resize>Without the bottom border
.navbar.no-border drops the bottom border. Use
when the navbar sits on a surface that already provides
visual separation or when the design calls for a borderless
top bar. .subnavbar.no-border is the same
exception for the secondary bar.
<docs-resize>
<header class="navbar no-border">
<div class="cluster is-between">
<a href="#"><strong>Acme</strong></a>
<div class="cluster">
<button class="button is-ghost" type="button">Help</button>
<button class="button" type="button">Account</button>
</div>
</div>
</header>
</docs-resize>Class reference
| Class | Role |
|---|---|
.navbar |
Sticky top bar. Surface + bottom border + padding. z-index set from the sticky token so
it layers above page content.
|
.subnavbar |
Secondary sticky bar. Offsets below the navbar via --navbar-height;
z-index sits one below the navbar so the two don't fight.
|
.navbar.no-border, .subnavbar.no-border |
Exception: drops the bottom border for designs that don't want the separator. |
Customization
.navbar and .subnavbar each expose a
scoped background / border / padding trio
(--navbar-bg, --navbar-border,
--navbar-padding and the three
--subnavbar-* counterparts). Override on a single
instance for a one-off tone or on :root for
site-wide retoning.
--navbar-height is the load-bearing coupling: the
subnavbar consumes it as its sticky offset. Unset, it falls
back to 3rem, which matches a single-line navbar
with snug padding. If you change the navbar's padding, font
size, or content height, set --navbar-height to
match the rendered height or the subnavbar will stick at the
wrong position.
Full token list and defaults:
src/css/navbar.css.
Accessibility
-
<header>is a landmark. The element exposes itself asbannerwhen it's a top-level page child, letting screen-reader users jump to it via landmark navigation. Put nav links inside an inner<nav>witharia-labelrather than on the<header>itself -- the banner landmark is for the bar as a whole; the nav landmark is for just the navigation region. - Sticky doesn't mean fixed. The navbar scrolls with the page until it reaches the top, then sticks. Users can scroll past it in a short viewport (e.g., a sidebar drawer on mobile) -- nothing special to wire, but be aware the top of the document may not always be "below the navbar" visually.
-
Skip link. A sticky navbar benefits from
a skip-to-main-content link as the first focusable
element -- keyboard and screen-reader users can bypass
the repeated nav on every page. Hide it until focused
with a
.visually-hidden-style utility. -
macOS keyboard quirk: Tab on macOS
skips
<a>links by default -- the OS only includes form controls in Tab order unless the user has enabled full keyboard navigation in their accessibility settings. Windows and Linux include links natively..nav-links in a navbar are focusable in every browser; the difference is whether Tab visits them. Expected platform behavior, not a brio limitation.
For a multi-component example mixing nav-link siblings, a Products dropdown, a Resources megamenu popover, a search form, and an icon-only notifications trigger inside a navbar, see the In a navbar section of dropdown's docs. Useful as a cross-browser reference for common navbar patterns and as a test case for component coexistence.
CSS Reference: Navbar
Sticky top bar
Source: src/css/navbar.css
Tokens
| Token | Default | Description |
|---|---|---|
--navbar-bg | var(--color-surface) | Background color for the navbar |
--navbar-border | var(--border-width-default) solid var(--color-border-subtle) | Bottom border for the navbar |
--navbar-padding | var(--ui-pad-y-snug) var(--ui-pad-x-snug) | Inner padding for the navbar |
Classes
| Class | Description |
|---|---|
.navbar | component root |
.no-border | Removes the bottom border |
CSS Reference: Subnavbar
Sticky secondary bar below the navbar
Source: src/css/navbar.css
Tokens
| Token | Default | Description |
|---|---|---|
--subnavbar-bg | var(--color-surface) | Background color for the subnavbar |
--subnavbar-border | var(--border-width-default) solid var(--color-border-subtle) | Bottom border for the subnavbar |
--subnavbar-padding | var(--ui-pad-y-compact) var(--ui-pad-x-snug) | Inner padding for the subnavbar |
--navbar-height | 4rem | Sticky offset from the top, set on navbar or :root |
Classes
| Class | Description |
|---|---|
.subnavbar | component root |
.no-border | Removes the bottom border |