Angular Navigation Menu Component | ng-hub-ui-nav

Angular navigation component with dropdowns, drill-down panels, responsive collapse, router integration and CSS-variable theming.

Last updated Sep 28, 2026

Overview

Why teams search for this library

Reach for this Angular navigation component when you need horizontal, vertical or drill-down menus with router awareness and reusable UX patterns.

Install

npm install ng-hub-ui-nav

Jump to

Ideal for

  • app shells
  • admin dashboards
  • documentation menus
  • responsive navigation

About nav

ng-hub-ui-nav is useful when Angular applications outgrow a simple top menu. It covers vertical sidebars, dropdown trees, nested navigation and responsive collapse patterns that appear in documentation sites, dashboards and enterprise shells.

Feature guides

Core Navigation

Essential horizontal and vertical navigation with data-driven item rendering, icons, badges, and disabled states.

Examples:
Basic Horizontal Navigation

Basic Horizontal Navigation

Vertical Accordion Navigation

Vertical Accordion Navigation

Vertical Flyout Navigation

Vertical Flyout Navigation

Dropdown Nesting

Unlimited multi-level dropdown menus with click, hover, or combined trigger modes. Includes separator and header items.

Examples:
Nested Multi-Level Dropdowns

Nested Multi-Level Dropdowns

Headers & Separators

Headers & Separators

Dropdown Triggers

Dropdown Triggers

States

Demonstrates disabled root and nested items and their interaction behavior.

Examples:
Disabled States

Disabled States

Truncated Labels & Tooltip

Truncated Labels & Tooltip

Narrow sidebar — hover a clipped item to read its full label.

Telling one entry apart

Telling one entry apart

Only Billing carries cssClass: 'nav-entry--overdue'. The class is written on that entry's hub-nav-item element, so one rule in your own stylesheet reaches it.

Responsive Behavior

Responsive collapsing, mobile rendering, and RTL slot behavior.

Examples:
Responsive Collapse Modes

Responsive Collapse Modes

Offcanvas mode

Click the toggler to open the mobile panel.

Dropdown mode

Click the toggler to open the mobile panel.

Fullscreen mode

Click the toggler to open the mobile panel.

RTL with Start/End Slots

RTL with Start/End Slots

Desktop Icon Rail

Collapse the vertical nav to an icon-only rail that keeps every section one click away: labels surface as tooltips, groups open as overlay flyouts, and the app owns the toggle and persistence through the two-way rail binding.

Examples:
Collapsed Icon Rail

Collapsed Icon Rail

Content area — the built-in toggle on the sidebar edge collapses the nav into an icon rail, returning horizontal space to data-dense screens. Hover a rail item to see its label; click a group to open its flyout.

Slots & Templates

Start/end slot projection and custom item templates to build tailored navigation UIs.

Examples:
Start & End Slots

Start & End Slots

Custom Item Template

Custom Item Template

Router Integration

Router links, query params, fragments and active-state matching through routerLinkActive options.

Examples:
Router Integration & Active States

Router Integration & Active States

This example uses real router links. Items pointing to /nav and /nav/api can show active state while browsing nav documentation tabs.

Travel needs a real navigation to travel between, so the live instance is the sidebar of this very page: click two library entries and watch the mark move. It is deliberately reserved for a choice — a scroll spy reports position by replacing the URL rather than pushing one, and following that stepped this documentation's own menu through twenty highlight changes in six seconds of ordinary reading. That is what followReplacedUrls governs: true follows every report, a number of milliseconds follows only once they go quiet (this site uses 400), and false never follows.

An entry that marks itself

An entry that marks itself

  • Pinned step — no route at all, active: true: marked.
  • Nav docs — on /nav, no flag: the router marks it.
  • Nav docs (opted out) — on /nav, active: false: refused.

In-page navigation

Marking an entry when the router is not what says where the reader is: `activeItemId`, the item's own `active` flag, and a scroll spy bound straight to the nav.

Examples:
In-page rail without routes

In-page rail without routes

activeItemId
summary
aria-current
—
address bar

Summary

What the agreement covers and who signs it.

Requirements

What each party has to provide before work starts.

Delivery

Milestones, acceptance and what happens when a date slips.

Support

Response times and the channels that carry them.

Scroll spy bound to the nav

Scroll spy bound to the nav

Scroll the column and the mark follows. Press an entry and it jumps — spin the wheel during the animation and the mark still lands where you asked.

Handbook

Onboarding

The first week: accounts, hardware and who to ask.

The sticky header above covers the first lines of whatever sits at the top, which is exactly what offset compensates — and what the old scrollIntoView jump had no way to take into account.

Expenses

What is reimbursed, with which receipt and by when.

The sticky header above covers the first lines of whatever sits at the top, which is exactly what offset compensates — and what the old scrollIntoView jump had no way to take into account.

Time off

Holiday, sick leave and the notice each one needs.

The sticky header above covers the first lines of whatever sits at the top, which is exactly what offset compensates — and what the old scrollIntoView jump had no way to take into account.

Equipment

What the company provides and how it is replaced.

The sticky header above covers the first lines of whatever sits at the top, which is exactly what offset compensates — and what the old scrollIntoView jump had no way to take into account.

Leaving

Notice periods, handover and returning the hardware.

The sticky header above covers the first lines of whatever sits at the top, which is exactly what offset compensates — and what the old scrollIntoView jump had no way to take into account.

Events

Observe interaction events for links, dropdowns, mobile toggles and panel navigation.

Examples:
Events API

Events API

Last event:

{
  "name": "none",
  "payload": null
}

Panel Drill-Down

Stacked panel navigation where clicking a dropdown opens its children in a side panel. Supports configurable max panels, drill-down with back navigation, and left/right sidebar positioning.

Examples:
Panel Drill-Down Navigation

Panel Drill-Down Navigation

Per-item expandMode Override

Per-item expandMode Override

Right Sidebar Panel Mode

Right Sidebar Panel Mode

Main content area — the sidebar is placed on the right and panels extend to the left.

Key features

Recent changes

Version 22.17.1 - 9/28/26, 12:00 AM

changed: The family list in both READMEs names `ng-hub-ui-spreadsheet`. The sheet joined the family and the other lists had not caught up. Documentation only: no code, types or styles change.

Version 22.17.0 - 9/24/26, 12:00 AM

fixed: BREAKING (visual): the label of a hovered or active entry is readable on the tint it sits on. Both painted the raw --hub-nav-accent, and an accent is chosen to be a colour, not ink: on the 12% wash the active entry sits on, the default primary measured 3.88:1, where WCAG AA asks 4.5:1. --hub-nav-accent-emphasis is now the accent steered into the theme's emphasis window instead of a percentage mixed over the theme ink — a percentage cannot darken a pale accent — and both labels read it. The same derivation now applies in the block that re-bases a variant's accent, which had been left behind and kept the defect for every custom accent. Hue and chroma are untouched. See BREAKING_CHANGES.md.

added: An entry's glyph can be projected instead of named with a class. HubNavItem.icon is a CSS class, which ties a menu to whichever icon font the page already loads and leaves an application on ng-hub-ui-icons keeping a second icon vocabulary alive just for its navigation. An ng-template hubNavItemIcon — or the matching [iconTemplate] input — draws that glyph instead, with the entry as its implicit context and a rail flag for the narrow column. The library takes no dependency on any icon package, and entries with no icon are untouched, so the rail initial still shows.

changed: BREAKING: a vertical nav no longer forces itself to the full width of its container. The component wrote width: 100% on its own host for every vertical orientation, which is invisible in a block container and ruinous anywhere else — a sidebar in a flex row or a grid track took the whole row and left its content nothing. The host stays at width: auto now; the rail keeps its explicit --hub-nav-rail-width. See BREAKING_CHANGES.md.

changed: BREAKING: HubNavPanelHistoryEntry carries the entry it came from (parentItem) instead of its label (parentLabel). Coming back out of a drill-down used to leave the panel claiming a parent it was not showing, so anything matching a panel to the menu by that id answered about the wrong level. See BREAKING_CHANGES.md.

fixed: An open panel follows the items input. A panel kept the children its entry had the moment it opened, so a menu that finished loading or a section that grew an entry left the panel painting the old list. Panels are re-read against the tree on every items change, keeping their ids so nothing is mounted again; a panel whose entry has left the menu closes, and takes the panels opened from inside it with it.

Version 22.16.0 - 9/23/26, 12:00 AM

added: An entry can be marked active without a route. [activeItemId] on hub-nav names the entry the reader is on, by its id or its fragment, and HubNavItem.active states it on the item itself. It answers for the whole menu while it is set — route matching stands down, so the mark cannot land on two entries at once — and it is two-way (activeItemIdChange), which is how the scroll spy drives it. active is per item and read as written, false included.

added: The scroll spy can be bound to a nav. [nav] joins the two halves: the section under the reader marks the matching entry and a click on an entry scrolls to its section. Neither touches the URL, so no router navigation and no history entry per section scrolled past.

added: The scroll spy takes the container it should scroll. [scrollContainer] names the element the sections move inside, for an application shell that scrolls its own column and for a container that only becomes scrollable once its content arrives.

added: A quiet period after a click. [clickSettleMs] keeps the section a click asked for while the jump is still in flight, so one notch of a wheel no longer hands the question back to the geometry mid-animation. Defaults to 0, which is the previous behaviour.

changed: BREAKING: itemClick now fires for every entry the reader can press, not only the ones with a route. An in-page rail reported nothing at all, which is why applications that needed those clicks went around the component. A handler that assumed item.route was there now has to check. Headers, separators and disabled entries still never emit. See BREAKING_CHANGES.md.

changed: BREAKING: scrollTo scrolls one container instead of asking the browser to bring the section into view. scrollIntoView scrolls every ancestor that can scroll, so a jump inside a column slid the shell under its own header. The jump compensates offset now, which scrollIntoView had no way to take. See BREAKING_CHANGES.md.

changed: Pressing an entry with no route closes the open flyout, as pressing one with a route already did.

fixed: HubNavItem.cssClass reaches the DOM. It had been declared on the model, and documented, since the first version, and nothing ever painted it — so the one hook for telling an entry apart from outside silently did nothing.

fixed: An entry with no route could not look active. Only the routable branches of the item template carried the active class, so even an explicitly marked entry rendered exactly like its neighbours.

fixed: aria-current says which kind of "here" it means. A mark the route gave is aria-current="page" as before; a mark the host gave is location, because the reader is here but the page did not change to say so. The dropdown toggle announces it too, which it never did.

Version 22.15.0 - 9/23/26, 12:00 AM

changed: BREAKING: the Angular floor rises from 17.2.0 to 17.3.0. The old range was measured from the source alone, and its published `.d.ts` names `InputSignalWithTransform` or `OutputEmitterRef`, which Angular did not ship until 17.3. An application below the new floor could install this package and then fail to build; it now gets the peer warning it should always have had. Nothing that worked stops working. See BREAKING_CHANGES.md.

Version 22.14.3 - 9/23/26, 12:00 AM

changed: The Angular peer range now says what the code needs. It asked for >=21.0.0, which nothing in this package justified: the newest Angular API the source uses is model() and the signal queries, which shipped in 17.2, and the compiled output carries no linker marker above it. The range is >=17.2.0, and npm run peers:floors derives and checks that floor from now on.

Version 22.14.2 - 9/16/26, 12:00 AM

changed: Repository, issue and README links follow the move to the hub-env organization. Issues for every Hub UI package are now gathered in hub-env/hub-ui, and the repository and bugs fields of the manifest point at the new addresses. No code, types or styles change.

Version 22.14.1 - 9/13/26, 12:00 AM

fixed: Every click on an entry of an open panel mounted that panel again: the route sync rebuilt the panel stack with new ids, the container tracks panels by id, and the panel replayed its entrance animation and its items jumped. Moving between the entries of the panels already open now keeps them and only re-reads their items.

Version 22.14.0 - 9/8/26, 12:00 AM

fixed: A nav with --hub-nav-border-radius came out rounded on the side its items are on and square on the side its drill-down panel was open, because the panel paints a fill of its own over the rail. The outermost panel now takes the nav own radius on its closing corners, and a right-hand sidebar takes it on the other side.

Version 22.13.0 - 9/8/26, 12:00 AM

added: --hub-nav-bg-image, so the bar can carry a gradient. The fill was only ever background-color, which takes a color and nothing else. A gradient is an image, so a var() holding one computes to an invalid value there and the declaration drops to unset: the bar ends up with no fill at all rather than falling back to the colour. The image now travels on its own property, layered over --hub-nav-bg, which keeps that colour as the fallback when the image is absent or fails. Nothing changes for a bar that does not set it.

Version 22.12.1 - 9/7/26, 12:00 AM

changed: The rail tooltip is drawn by [hubTooltip]. A collapsed rail hides the item labels and puts each one in a tooltip; that tooltip came from [tooltip], which ng-hub-ui-utils removes in 22.14.0 because a bare attribute is a name in the application namespace rather than a library one. The prefixed directive is the same tooltip, so nothing about the rail changes.

Version 22.12.0 - 9/7/26, 12:00 AM

fixed: The flyout panel stayed hanging where its trigger had been. In a menu that mixes the two expansions, opening a floating section closes the accordion above it and everything underneath rises to fill the gap, the trigger included. Neither of the two things the overlay watched, the page scrolling and the window resizing, happens there, so the panel kept the height the trigger had before the collapse and was left orphaned halfway down the menu. The collapse is animated, so it is not a jump either: the trigger slides for the length of the transition and the panel now travels with it the whole way. Fixed in ng-hub-ui-utils 22.13.0, which is what this release requires.

changed: ng-hub-ui-utils peer floor raised to >=22.13.0, which is where the overlay learned to follow a trigger that moves. Below it the flyout panel still parts company with its item, so the floor is what keeps the fix above from being a promise the installed packages cannot keep. Raising a floor is also what makes this release a minor rather than the patch it started as; the fix itself changes no public shape in this library.

fixed: A mixed menu left the panel of the section you had just walked out of on screen. Where one root drills down into panels and another expands in place, landing on the accordion root synchronised the dropdowns and left the panel stack alone, and since an accordion never touches that stack nothing ever closed it: two sections were open at once, one of them the section just left. The mirror image was true too, a panel root leaving the previous accordion expanded beside its new panel. Each root now clears whatever the other opened, while moving inside the root you are already in still leaves that section standing.

fixed: A first entry sitting on the language prefix swallowed every route match. The active root was taken to be the first item matching the URL, and an item routed at /en/ matches every page in the site, so the section actually holding the route was never the one resolved: accordion sections stayed shut on every page and their navigation was handed to the panel stack instead. The active root and the trail of open dropdowns now both resolve by the longest matching route, the rule the panel opener already followed.

fixed: A page outside the menu left the last section marked open. Panels were cleared when no item matched the URL, the dropdowns were not, so the nav went on claiming the reader was inside a section they had left. Both are cleared now, and likewise when the route lands on a root with no children of its own.

changed: closeAllDropdowns() and closeAllPanels() on HubNavStateService skip the write when there is nothing open. Route synchronisation calls both on every navigation, and a signal has no value equality, so writing an empty set over an empty set woke every reader for nothing.

Version 22.11.3 - 9/6/26, 12:00 AM

fixed: A section marked the way the README teaches was never tracked. A valueless hubNavScrollSpySection attribute reaches the directive as the empty string rather than as null, which is how Angular initialises a static attribute with no value, so the nullish fallback to the host id never ran: the host marker attribute was stripped and the container found no sections at all. The spy stayed silent, with no activeSectionChange and nothing in the console to explain it, and consumers had to bind the id twice, once as id and once as [hubNavScrollSpySection], to get back the behaviour the directive already promised. The fallback is now falsy-aware, so the documented bare form works and the explicit form is unchanged.

Version 22.11.2 - 9/2/26, 12:00 AM

fixed: A sidebar came back from mobile without its section panel. Narrowing the window below collapseBreakpoint and widening it again left the nav expanded but panel-less: crossing back up emptied the panel stack, and nothing refilled it, because panels are derived from the route only on navigation. The user had to navigate somewhere else, anywhere, to get back the section they were already in. The stack is now re-derived from the current URL rather than left empty - not merely kept, because while collapsed the offcanvas navigates without touching the panels, so what survived would describe the page the user left. With autoOpenFromRoute off the stack is whatever the user opened by hand and nothing could rebuild it, so it is left untouched instead.

Version 22.11.1 - 9/1/26, 12:00 AM

changed: The homepage in the manifest points at this library's own documentation page rather than at the site root. Metadata only.

Version 22.11.0 - 8/19/26, 12:00 AM

added: isLast on hub-nav-panel, marking the outermost panel of a vertical stack so it can close the stack's edge.

added: --hub-nav-panel-last-shadow (default none), the shadow of that terminal panel. Dropped by default because that panel now closes its edge with a border, and the boundary should be drawn once.

Version 22.10.0 - 8/16/26, 12:00 AM

fixed: The scroll spy could never reach the last sections of a page: its observation band stops partway down the viewport, so once the container has nothing left to scroll, everything below that line was unreachable — clicking the final entry of a seven-example page settled the mark four items above it. The last section now wins outright at the end of the container. Selection also went by intersectionRatio, which measures how much of a section fits the band rather than which comes first, so a short block beat the tall one the reader was in the middle of; the topmost section wins now.

added: config.followReplacedUrls sets how eagerly the nav follows a scroll spy, which names the section under the reader by replacing the URL: true follows each report (the previous behaviour and the default), a number of milliseconds follows only once the reports go quiet so a scroll lands the mark once, and false never follows. On a thirty-item panel that turns twenty highlight changes in six seconds of reading into one. Alongside it, the active mark can travel between items instead of appearing and disappearing in place (config.activeIndicator, off by default). One element per list, parked over whichever sibling is active and moved with a transform, so the animation never touches the layout; duration and easing come from the new --hub-nav-item-active-indicator-transition. Opt-in because the mark is normally painted by each item, so turning it on moves the same pixels to a node the list owns. Travelling says you chose to go here, and most moves are not that: the mark travels for a pushed navigation and arrives for a replaced one, which is how a scroll spy reports position — otherwise it flies up and down a long panel while the reader is only reading. A relayout is treated the same way. Travel only means something between siblings: crossing into a submenu, a drill-down panel or a collapsed rail swaps one list for another, so each list owns its mark and it fades in there. Geometry is measured and re-measured through a ResizeObserver, since the rail, the accordion and the viewport all resize items without the list hearing about it. Honours prefers-reduced-motion.

Version 22.9.1 - 8/16/26, 12:00 AM

fixed: An item stopped being marked active when the application serialized URLs with a trailing slash. /products and /products/ are the same place, and a canonical trailing-slash UrlSerializer is an ordinary SEO choice, but route matching compared the two as raw strings — so every item declared without the slash silently stopped matching, and only its ancestors stayed lit because their prefix test tolerates one. Worst on items differing from their siblings only by fragment: none ever matched, so a scroll-spy panel appeared dead. Paths are now compared normalized, root excluded.

Version 22.9.0 - 8/15/26, 12:00 AM

added: Desktop icon rail: the new two-way rail input collapses a vertical nav to --hub-nav-rail-width showing icons only. A built-in edge toggle (config.railToggle, default true, fully themeable through the --hub-nav-rail-toggle-* tokens including a replaceable SVG arrow) flips the state; labels surface as tooltips, accordion groups open as click-triggered overlay flyouts, and below collapseBreakpoint the offcanvas behavior always wins. The library persists nothing — railChange lets the host store the preference.

added: Localizable built-in labels: the previously hardcoded ARIA strings (Toggle navigation, Close navigation, Go back, Close panel, Toggle {label}) now resolve from HUBUI.NAV.* keys via the shared translation adapter, with per-instance overrides through config.labels.

Version 22.8.4 - 8/13/26, 12:00 AM

fixed: Route-aware panel sidebars now choose the most specific active root item when opening panels. A localized Home route such as /en/ no longer wins over /en/forms/overview/, so deep links keep the active library secondary panel open instead of leaving only its root entry highlighted.

Version 22.8.3 - 8/8/26, 12:00 AM

fixed: Documentation links now point at the canonical localized URLs. The README linked to https://hubui.dev/<path> with no locale prefix and no trailing slash, and both forms are 301-redirected, so every reader arriving from npm or GitHub landed on a redirect instead of the canonical page.

Version 22.8.2 - 8/7/26, 12:00 AM

fixed: A section no longer blinks when you click inside it. Clicking an entry closed every dropdown — right for a flyout, which is transient and dismissed by the click — but an accordion opened from the route states where you are, so closing it and letting the route reopen it a tick later made the section shut and open again on every navigation. The click now leaves the dropdown state alone when the route owns it (autoOpenFromRoute on an accordion) and still dismisses a flyout.

Version 22.8.1 - 8/7/26, 12:00 AM

fixed: An accordion opens the section you navigated into. Opening from the route was decided by whether the rail was collapsed and nothing else, so a vertical accordion arrived at /products with its section shut, while the panel it opened instead sat behind the page, invisible to the eye and the pointer. The expanded rail now consults the effective expand mode of the section holding the active route, so an accordion syncs its dropdowns and only a flyout or panel rail opens panels.

fixed: Two entries no longer claim to be where you are. Marking a section on everything below it also marked an entry whose route prefixes a sibling's: at /products/categories, both "Products" (/products) and "Categories" lit up. The longest matching route among siblings wins now, so the catalogue is marked there and the list is still marked at /products/42/edit. An exact match is never overridden, and a dropdown that matched through a child keeps its mark.

Version 22.8.0 - 8/6/26, 12:00 AM

fixed: The rail keeps its mark on a detail page. An item was active only on its exact route, so opening a record — /customers/42/edit from /customers — cleared the mark. An item is now active on its own route and on anything below it, matched by whole segments (/products is not marked by /products-archive), with the query string ignored. A root item (/) still matches only itself, and routerLinkActiveOptions: { exact: true } — declared but never read until now — opts an item back into strict matching. Dropdowns follow their children.

Version 22.7.2 - 7/27/26, 12:00 AM

fixed: aria-orientation on the menu tree: the root menubar now announces vertical when the nav renders as a sidebar (WAI-ARIA defaults menubar to horizontal), and submenu/dropdown menus declare vertical explicitly. Keyboard behavior was already orientation-aware — only the attribute was missing.

Version 22.7.1 - 7/26/26, 12:00 AM

fixed: Declared the real ng-hub-ui-utils peer range (>=22.7.0); the previous >=1.0.0 floor resolved to a utils major without resolveHubAccent.

Version 22.7.0 - 7/8/26, 12:00 AM

changed: BREAKING — the semantic accent input variant is renamed to color, for consistency with the rest of the hub-ui family (hub-button, hub-badge, hub-metrics, hub-milestone, all color). Migration: <hub-nav variant="primary"> becomes <hub-nav color="primary">. The internal data-variant attribute and the :host([data-variant=…]) token rules are unchanged.

added: The offcanvas mobile drawer forwards hubNavItemTemplate and projects the hubNavStart / hubNavEnd slots, so it matches the desktop nav instead of rendering bare items. New HubNavMobilePanelComponent inputs: itemTemplate, startTemplate, endTemplate.

added: inDrawer slot context on HubNavStartTemplateContext / HubNavEndTemplateContext, true only inside the offcanvas drawer, so a full header can render there while the collapsed top bar stays slim.

added: --hub-nav-collapsed-justify (justify-content of the collapsed top-bar row), plus --hub-nav-border-radius and --hub-nav-box-shadow, so the whole nav can become a floating card through tokens alone.

fixed: The vertical primary column no longer pins --hub-nav-panel-width when there are no panels, which overflowed a narrower rail; it fills its container and only pins the fixed width under the new .hub-nav--has-panels host class.

Version 22.6.0 - 7/7/26, 12:00 AM

added: hub-nav-theme(...) SCSS mixin — one-call token theming for hub-nav: $accent feeds the single --hub-nav-accent slot and the component derives the rest of the role family, alongside the bar surface and the item appearance.

changed: Packaging — the library ships its SCSS at /styles, so the mixin is a first-class package entry: @use 'ng-hub-ui-nav/styles' as *;.

Version 22.5.0 - 7/7/26, 12:00 AM

changed: The accent input accepts ANY colour: on top of the built-in semantic accents it now takes a registered custom accent and a literal colour (#hex, rgb(), oklch(), a CSS named colour), resolved through resolveHubAccent from ng-hub-ui-utils.

changed: Internal — host bindings moved from @HostBinding / @HostListener decorators to the host metadata object, per the Angular style guide. No public API or behaviour change.

Version 22.4.0 - 7/2/26, 12:00 AM

changed: --hub-nav-accent-subtle now uses the canonical design-system derivation (a 12% mix instead of 14%), so a custom accent re-derives the same role family a built-in one gets.

fixed: SSR/prerender no longer logs "requestAnimationFrame is not defined": the scroll-spy directive scheduled its IntersectionObserver setup through requestAnimationFrame on every platform, and is now inert outside the browser.

fixed: Nav transitions actually run when the ds tokens are loaded. The ds transition tokens are complete transition values, but the components composed them after a property name, producing an invalid declaration that silently disabled every item, caret, panel and mobile transition. The accordion animation and the panel transition were fixed the same way.

Version 22.3.0 - 6/29/26, 12:00 AM

added: Tooltip on truncated item labels: a label clipped with an ellipsis reveals its full text on hover, through ng-hub-ui-utils' [hubOverflowTooltip] and only when the label actually overflows. The tooltip is agnostic and can be swapped with provideHubTooltip(...).

Version 22.2.0 - 6/26/26, 12:00 AM

added: Open-set accent variants: the accent input now takes the full open accent set out of the box, and any other name works with no recompile — define a single --hub-sys-color-<name> and the whole hover/active treatment derives from it.

added: Derived accent roles --hub-nav-accent-emphasis (accent mixed over the theme ink) and --hub-nav-accent-on (the contrast colour for text sitting on the accent). Both follow the active accent automatically.

changed: BREAKING — canonical zindex token names: --hub-nav-dropdown-z-index becomes --hub-nav-dropdown-zindex, --hub-nav-mobile-z-index becomes --hub-nav-mobile-zindex, and --hub-nav-panel-z-index becomes --hub-nav-panel-zindex, matching the --hub-sys-zindex-* convention.

changed: All accent derivations now interpolate in the OKLCH colour space for perceptually even tints across every accent. No token API change; tints shift very slightly.

Version 22.1.1 - 6/25/26, 12:00 AM

fixed: Design-token consistency pass: inline fallback defaults aligned with the canonical ng-hub-ui-ds values, and hardcoded literals routed through their --hub-sys-* / --hub-ref-* tokens so they follow the active theme. No visual change when the ds tokens are loaded.

Version 22.1.0 - 6/24/26, 12:00 AM

added: The semantic accent input on hub-nav, selecting the accent of the hover/active affordances. The built-in accents render with the exact design-system tints, and any other string is accepted too — the nav reads --hub-sys-color-<name> from the host application.

added: --hub-nav-accent (defaults to --hub-sys-color-primary): --hub-nav-item-hover-color and --hub-nav-item-active-bg now resolve through this single accent instead of being hard-wired to the primary colour.

added: Richer accent treatment for hover/active: soft color-mix tints (--hub-nav-accent-subtle), accent text, and — in a horizontal navbar only — an accent indicator bar along the bottom edge (--hub-nav-item-active-indicator-color, --hub-nav-item-active-indicator-size).

changed: BREAKING (visual) — the active item moved from a solid accent fill with white text to a soft accent tint with accent text, the hover background is an accent tint instead of neutral grey, and the nav surface carries a faint accent wash. Override --hub-nav-item-active-bg / --hub-nav-item-active-color / --hub-nav-item-hover-bg / --hub-nav-bg to restore the previous look.

fixed: Cross-layer token references aligned with the canonical ng-hub-ui-ds names, so the components follow the theme instead of only their inline fallback.

Version 22.0.0 - 6/17/26, 12:00 AM

changed: Aligned with Angular 22, and README documentation standardized.

Version 21.1.1 - 4/12/26, 12:00 AM

fixed: Panel mode when a nav item has no dedicated caret: the panel now opens on a click anywhere on the label.

Version 21.1.0 - 4/1/26, 12:00 AM

added: Panel drill-down expand mode with stacked side panels, configurable max visible panels, and drill-down with back navigation.

added: Per-item expandMode override (accordion, flyout, or panel) on HubNavItem.

added: Sidebar positioning with sidebarSide config (left or right).

added: Panel keyboard navigation (Escape, ArrowLeft) and automatic focus management.

added: Mobile fallback: panel mode degrades to accordion when collapsed.

added: CSS custom properties for panel styling (--hub-nav-panel-*).

Version 21.0.0 - 3/19/26, 12:00 AM

added: Initial release with horizontal and vertical navigation.

added: Data-driven API with HubNavItem interface supporting link, dropdown, header, separator, and custom types.

added: Unlimited dropdown nesting with recursive rendering.

added: Responsive collapsing with offcanvas, dropdown, and fullscreen mobile panel modes.

added: Vertical accordion and flyout expand modes.

added: Start/end slots via hubNavStart and hubNavEnd directives, plus custom item templates via hubNavItemTemplate.

added: Full Angular Router integration with routerLink, routerLinkActive, fragment, and queryParams support.

added: WCAG 2.1 AA keyboard navigation with WAI-ARIA menubar/menu pattern.

added: Complete CSS custom properties token set with 60+ variables.

added: HubNavConfigService with InjectionToken for global default configuration.

Frequently asked questions

How do I build an Angular navigation menu from a data structure?

Install ng-hub-ui-nav and give <hub-nav> an array of HubNavItem objects through [items]. Each item carries a label, an optional route as a string or as route segments, children for its submenu, and a type of link, dropdown, header, separator or custom. The component renders the whole tree, so there is no toolbar-plus-list markup to assemble by hand.

Can I use it as a sidebar that collapses to icons?

Yes. Set orientation to vertical in [config] and bind [(rail)] to a boolean of your own: while it is true the sidebar shrinks to --hub-nav-rail-width, labels become tooltips and accordion groups turn into click-triggered flyouts. The library persists nothing, so storing that preference is your job — railChange tells you when it flips.

How does it decide which item is active?

It computes that itself instead of using routerLinkActive. Matching is by whole path segments, so /products is not matched by /products-archive, the query string is ignored, a parent dropdown follows its active child, and only the most specific match is marked — with the .hub-nav-item__link--active class and aria-current="page". Set routerLinkActiveOptions: { exact: true } on an item to match only its own route.

Do I need Angular Router to use it?

Yes. @angular/router is a hard peer dependency and link items render as anchors with [routerLink], so there is no router-free mode for a nav pointing only at external URLs. It also ships no item search, no virtual scrolling and no drag-to-reorder: compared with assembling MatToolbar, MatSidenav and MatNavList by hand, what you get is one data-driven component and around 135 --hub-nav-* custom properties instead of a Sass theme.