Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Top-level site/app navigation primitive. HTML-first wrap or JSON-first .items setter; auto aria-current sync via pathname matching with popstate listener.
The <nav-bar> component is the top-level navigation primitive for VB sites and apps. It does one thing: synchronizes aria-current="page" on the matching link as the URL changes, so consumers stop hand-rolling that logic. There are two ways to use it:
<a> children — pathname matching and popstate sync run automatically..items = [...] and let the default renderer build the link list from data.It is deliberately small — no router, no click interception, no custom renderer in v1. See Reactive integration for the trade-offs.
Wrap a normal set of links. Without JS the nav still works as plain HTML; with JS, aria-current="page" appears on the link that best matches location.pathname.
<nav-bar aria-label="Main"> <a href="/">Home</a> <a class="active-parent" href="/docs/">Docs</a> <a class="active-parent" href="/docs/elements/">Elements</a> <a href="/changelog/">Changelog</a></nav-bar>
Matching policy:
href prefix wins. So on /docs/elements/web-components/nav-bar/ the link /docs/elements/ beats /docs/.Add data-match="exact" on the <nav-bar> to opt out of prefix matching.
<drop-down> or <li>)By default nav-bar only manages direct <a> children. If your link list is wrapped (each item inside a <li>, or each section in a <drop-down>), tag the canonical link in each item with data-nav-link:
<nav-bar aria-label="Main"> <ul> <li> <drop-down hover=""> <a href="/docs/quick-start/" data-nav-link="" data-trigger="">Start</a> <menu>...</menu> </drop-down> </li> </ul></nav-bar>
When any descendant link carries data-nav-link, those become the candidate set; otherwise direct-children behavior applies.
When your routes live in JSON or are produced by code, assign .items. The component renders <a> children with the default markup.
const nav = document.querySelector('nav-bar'); await new Promise(r => nav.addEventListener('nav-bar:upgraded', r, { once: true })); nav.items = [ { href: '/', label: 'Home' }, { href: '/dashboard/', label: 'Dashboard', icon: 'gauge' }, { href: '/settings/', label: 'Settings' }, { href: 'https://github.com/...', label: 'Repo', external: true },];
The setter is idempotent — assigning a deep-equal array is a no-op and emits no event.
| Field | Type | Description |
|---|---|---|
href | string | Required. Anchor target. |
label | string | Required. Link text. |
route | string | Optional. .current diff target. Falls back to href. |
icon | string | Optional. Renders <icon-wc name="..."> before the label. |
external | boolean | Adds target="_blank", rel="noopener noreferrer", and an SR-only "(opens in new tab)" suffix. |
badge | string | number | Optional pill chip after the label. |
| Property | Type | Description |
|---|---|---|
.items | NavItem[] | null | Read returns last assignment, else inferred from children. Set replaces children with default renderer; idempotent. |
.current | string | null | Read returns the active route (data-route or href of the aria-current link). Set takes ownership and disables popstate sync. |
| Method | Description |
|---|---|
.releaseCurrent() | Restore auto mode after explicit assignment to .current. Re-runs pathname matching. |
.refresh() | Manually re-run pathname matching. Useful for AJAX frameworks (e.g. HTML-Star) that change the URL without firing popstate. No-op when .current is explicitly owned. |
| Event | Detail | When |
|---|---|---|
nav-bar:upgraded | — | Once after first connect. |
nav-bar:items-changed | { items, source: 'property' } | After .items is assigned to a new value. |
nav-bar:current-changed | { current, previous, source } | source is one of 'property' | 'popstate' | 'pathname'. |
v1 does not support driving .items from a signal/effect with preserved in-flight state. Each .items assignment fully replaces children. There is no keyed diff, so a link mid-focus loses focus on reassignment, and any <icon-wc> inside re-mounts. This is fine for nav (links don't carry load-bearing state) but it is not the right primitive for reactive route lists.
If you need reactive routing with preserved state, use your framework's link primitive instead of <nav-bar>:
Link() from montane/router.js. It is bound to the Montane router and reactively tracks active state. <nav-bar> would duplicate that work less well.<content-swap>.Link primitive. <nav-bar> is a drop-in for HTML-first sites and JSON-driven apps without a reactive router.If your use case needs renderItem or keyed diff, open an issue with the concrete scenario so we can scope a Phase 2.
<mobile-menu> — nav-bar is content; mobile-menu is the off-canvas shell. They compose: <mobile-menu><nav-bar>...</nav-bar></mobile-menu>.<drop-down> — wrap an item in <drop-down> for a sub-menu. nav-bar only manages its direct <a> children.role="navigation" on the host if absent.aria-current="page" on the active link only.target="_blank", rel="noopener noreferrer", and an SR-only "(opens in new tab)" suffix.