@stackra/navigation
v2.0.0
Published
Navigation surfaces for the Stackra framework — sidebar, navbar, footer, breadcrumb, tabs, mega-menu, mobile-bar, command palette, account-menu, and banner components composed from HeroUI Pro primitives with a rich menu schema (nested, sectioned, gated by
Maintainers
Readme
@stackra/navigation
Navigation surfaces for the Stackra framework — sidebar, navbar, footer, breadcrumb, tabs, mega-menu, mobile-bar, command palette, account-menu, and banner components composed from HeroUI Pro primitives, with a rich menu schema and a DI-first menu registry.
What it ships
Sixteen navigation components composed on top of HeroUI Pro's Sidebar,
Navbar, AppLayout, Resizable, Popover, Menu, Tabs, Dropdown, and
ScrollShadow primitives. Every surface reads menu contributions from the
workspace's shared IMenuRegistry, gates them against the caller's
INavigationContext, and lets other packages plug in via
NavigationModule.forFeature({ menus }).
Component surface
| Component | Purpose |
| ------------------- | ------------------------------------------------------------------- |
| <NavSidebar> | Vertical sidebar shell. Draggable, resizable, collapsible. |
| <NavHeader> | Sticky top navbar on HeroUI Pro's Navbar primitive. |
| <NavFooter> | Site footer with columns, copyright, 7 placement variants. |
| <NavBreadcrumb> | Route-driven breadcrumb chain. |
| <NavTabs> | Horizontal tab bar with active-item highlighting. |
| <NavMegaMenu> | Multi-column dropdown for wide navigation menus. |
| <NavAccountMenu> | Avatar + primary-account actions dropdown. |
| <NavMobileBar> | Bottom-of-viewport mobile bar with icon-driven items. |
| <NavCommand> | Command-palette overlay with keyboard shortcuts. |
| <NavBanner> | Dismissible banner (info / success / warning / danger / promo). |
| <NavMenu> | Top-level menu renderer — horizontal / sidebar / drawer / columns. |
| <NavItem> | Single-menu-item dispatcher; walks the IMenuItem.kind union. |
| <NavRouteSource> | Discovers routes from @stackra/routing and hydrates the registry. |
| <NavSearch> | Inline search input for the header / sidebar. |
| <NavBlock> | Optional block registry for arbitrary sidebar content. |
| <SidebarProvider> | Standalone context provider for sidebar-aware components. |
Menu schema (IMenuItem)
A menu item is a discriminated union — kind decides which primitive renders:
"link"— internal router<Link>(via@stackra/routing/react)."external"— external<a>with automatictarget="_blank"+rel."action"— dispatches an@stackra/actionshandler."resource"— resource-aware<Link>(list / show / edit / create)."separator"— divider between items."header"— static section heading."group"— nested collection with children."dropdown"— popover with children."mega-menu"— column-layout dropdown."banner"— inline banner."custom"— render callback owns the node.
Every item can carry: badge, tags, description, icon (left + right), keyboard
shortcut, custom className, containerClassName, labelClassName, inline
style, per-breakpoint suppression (hideOn), auth gate (auth), required
permission(s) / feature(s), sync visibility predicate (when(ctx)), render
override, sort order, and analytics event / props.
See
@stackra/contracts/interfaces/navigation
for the full schema.
Install
The package is @stackra/navigation. In this monorepo, add it as a workspace
peer:
{
"peerDependencies": {
"@stackra/navigation": "workspace:^",
},
"peerDependenciesMeta": {
"@stackra/navigation": { "optional": true },
},
"devDependencies": {
"@stackra/navigation": "workspace:*",
},
}Wire it up
Mount NavigationModule.forRoot() once at the application root:
import { ApplicationFactory } from "@stackra/container";
import { NavigationModule } from "@stackra/navigation";
const app = await ApplicationFactory.create({
imports: [
NavigationModule.forRoot({
sidebar: {
variant: "sidebar",
collapsible: "icon",
defaultCollapsed: false,
resizable: false,
location: "primary",
},
header: {
position: "sticky",
maxWidth: "xl",
startLocation: "primary",
endLocation: "header-end",
},
footer: {
location: "footer",
placement: "expanded",
showBackToTop: true,
copyrightHolder: "Acme Inc.",
},
}),
],
});Contribute menus from any WebXModule.forRoot() via .forFeature({ menus }):
import {
Injectable,
Module,
type DynamicModule,
type OnApplicationBootstrap,
} from "@stackra/container";
import { MENU_REGISTRY, type IMenuRegistry } from "@stackra/contracts";
import { NavigationModule } from "@stackra/navigation";
import { ShieldCheck } from "@stackra/ui/icons/outline";
import { RbacModule } from "@/core/rbac.module";
@Module({})
export class WebRbacModule {
public static forRoot(): DynamicModule {
@Injectable()
class RbacMenusRegistrar implements OnApplicationBootstrap {
public constructor(
@Inject(MENU_REGISTRY) private readonly registry: IMenuRegistry,
) {}
public onApplicationBootstrap(): void {
this.registry.register("primary", {
menu: {
id: "rbac-primary",
location: "primary",
items: [
{
id: "roles",
kind: "link",
label: "Roles",
to: "/rbac/roles",
icon: ShieldCheck,
requiresPermission: "rbac.roles.view",
},
{
id: "permissions",
kind: "link",
label: "Permissions",
to: "/rbac/permissions",
requiresPermission: "rbac.permissions.view",
},
],
},
priority: 100,
});
}
}
return {
module: WebRbacModule,
imports: [RbacModule.forRoot()],
providers: [RbacMenusRegistrar],
};
}
}Sidebar as an AppLayout slot
The sidebar plays cleanly with HeroUI Pro's AppLayout component — pass it in
the sidebar slot and every layout prop (resizable, collapsible, side, variant)
is honoured by AppLayout's internal Sidebar.Provider:
import { AppLayout } from "@stackra/ui/react";
import { NavSidebar, NavHeader, NavFooter } from "@stackra/navigation/react";
export function AppShell({ children }: { children: React.ReactNode }) {
return (
<AppLayout
sidebar={<NavSidebar />}
navbar={<NavHeader />}
sidebarVariant="sidebar"
sidebarCollapsible="icon"
sidebarResizable
sidebarDefaultSize="16rem"
sidebarMinSize="12rem"
sidebarMaxSize="24rem"
resizableAutoSaveId="stackra:app-sidebar"
footer={<NavFooter />}
>
{children}
</AppLayout>
);
}Or use <NavSidebar> standalone — it renders its own Sidebar.Provider and can
sit anywhere in the tree.
Testing
Import the testing helpers to mount a controllable menu registry in tests:
import { TestNavigationProvider } from "@stackra/navigation/testing";
render(
<TestNavigationProvider
context={{ authenticated: true, permissions: ["rbac.roles.view"] }}
menus={{
primary: [
{ id: "roles", kind: "link", label: "Roles", to: "/rbac/roles" },
],
}}
>
<NavSidebar />
</TestNavigationProvider>,
);Cross-references
.kiro/steering/navigation-catalog.md— the enforcement rules every consumer follows..kiro/specs/navigation-day-one/design.md— architecture spec: registry design, injection contract, surface anatomy..kiro/plans/navigation-workspace-inventory.md— per-app menu inventory + cross-package injection graph.- ADR-0052 —
.forFeature()registrar-class pattern.
