0.5.0 is the first element quality release, covering every A, B and C element. Components rebuild cleanly when moved or re-populated, keyboard and screen-reader access is fixed across the C components, and several XSS paths are closed. Themes gain a --btn-border-width hook,…
Top-level site/app navigation primitive. HTML-first wrap or JSON-first .items setter; auto aria-current sync via pathname matching with popstate listener.
Overview
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:
HTML-first: wrap your existing <a> children — pathname matching and popstate sync run automatically.
JS-first: assign .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.
HTML-first
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.
Otherwise, longest href prefix wins. So on /docs/elements/web-components/nav-bar/ the link /docs/elements/ beats /docs/.
Ties broken by first-in-DOM-order.
Add data-match="exact" on the <nav-bar> to opt out of prefix matching.
Nested links (e.g. inside <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:
The setter is idempotent — assigning a deep-equal array is a no-op and emits no event.
Item shape
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.
Properties
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.
Methods
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.
Events
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'.
Reactive integration (important caveat)
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>:
Montane — use 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.