Skip to content

Headless Core — Navbar

Powers Navbar internally. The most feature-rich navigation factory — beyond simple active-item tracking, it can derive the active item from scroll position, a URL hash, or a route pathname, so a single config drives a marketing-site nav, a docs sidebar-style top nav, or an SPA router nav.

import { createNavbar } from '@andersseen/headless-components/navbar';
const navbar = createNavbar({
items: [
{ id: 'home', label: 'Home', href: '#hero' },
{ id: 'features', label: 'Features', href: '#features' },
],
defaultActiveItem: 'home',
onActiveItemChange: id => console.log('Active:', id),
});
element.addEventListener('keydown', e => navbar.handleItemKeyDown(e, 'home'));
window.addEventListener('scroll', () =>
navbar.actions.updateActiveFromScroll(),
);
OptionTypeDefaultNotes
itemsNavbarItem[][]{ id, label, href?, target?, icon?, disabled? }.
defaultActiveItemstringfirst item id, or 'home'
onActiveItemChange(id: string) => void
mobileMenuOpenbooleanfalse
onMobileMenuChange(open: boolean) => void
scrollSpybooleanfalseItems must have href starting with # to participate.
scrollSpyOffsetnumber (px)100Section counts as “in view” once scrolled past this line from the top.
ariaLabelstring'Main navigation'
routeMatchMode'exact' | 'prefix''prefix'prefix matches nested routes (/docs/intro/docs), preferring the longest match.
getElementByHash(hash: string) => Element | nulldocument.querySelectorAdapter so the headless core never touches the DOM directly.
getLocation() => { hash: string; pathname: string }window.locationSame reasoning — swap in a router’s location for SPA frameworks.

activeItem, mobileMenuOpen, itemIds: string[].

MemberSignatureNotes
setActiveItem(id)(itemId: string) => voidNo-ops for a disabled item.
toggleMobileMenu() / setMobileMenuOpen(v) / closeMobileMenu()() => void / (boolean) => void / () => void
setItems(items)(items: NavbarItem[]) => voidRe-registers the item list; resets activeItem if it’s no longer present.
updateActiveFromScroll(offset?)(offset?: number) => voidCall from a scroll listener.
updateActiveFromHash()() => voidCall on load / hashchange.
updateActiveFromRoute(pathname?, mode?)(pathname?: string, routeMatchMode?: 'exact' | 'prefix') => voidCall on route change; pass pathname explicitly in SPA frameworks.

isActive(id), getActiveItem(), isDisabled(id), getItemIds().

GetterReturns
getContainerProps()role: 'navigation', aria-label
getNavListProps()role: 'menubar', aria-label
getItemProps(id, { href?, target? })role: 'menuitem', aria-current ('page' when active), aria-disabled, data-active, data-state, tabindex (roving), id, plus href/target if passed
getToggleProps()aria-expanded, aria-label (swaps text open/closed), aria-controls, role: 'button', tabindex: 0, data-state
getMobileMenuProps()id, role: 'menu', aria-label, data-state, hidden

handleItemKeyDown(event, currentItemId)ArrowRight/ArrowLeft move with wraparound (skipping disabled items), Home/End jump, Enter/Space activate the current item and close the mobile menu, Escape closes the mobile menu.

Home Features Toggle mobile menu

Sidebar is the same active-item-tracking idea for a vertical side panel, with a collapse toggle instead of scroll-spy/route detection.