Sidebar Nav
FreeThe reusable half of a dashboard sidebar: grouped items, sub-navigation, a collapsed rail and a mobile drawer — with no router dependency and no brand welded in. Bring your own header and footer.
</>sv-sidebar-nav sidebarnavigationshellraildrawermenudashboard
import { SvSidebarNavComponent } from '@styloviz/sidebar-nav';Demo & usage
Basic usage
Usage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
With projected chrome
example.htmlAngular
4 lines
<sv-sidebar-nav [menuGroups]="groups" [activeRoute]="router.url"> <a slot="header" href="/">Acme</a> <span slot="footer">v2.4</span> </sv-sidebar-nav>
Collapsed rail, no footer
example.htmlAngular
1 lines
<sv-sidebar-nav [menuGroups]="groups" [collapsed]="true" [showFooter]="false" />
Wiring it to the Angular router
example.htmlAngular
10 lines
// One provider, once, at the app root. The component never imports the // router itself — that is what makes it usable in a hybrid app. provideSvLinkAdapter(() => { const router = inject(Router); return { kind: 'router', href: (t) => router.serializeUrl(router.createUrlTree([t].flat())), navigate: (t) => { void router.navigateByUrl(`${t}`); return true; }, }; })
When to use
- The primary navigation of a dashboard, admin panel or internal tool.
- You want the rail and the mobile drawer without writing route-sync three times.
- Your app does not route the way Angular's router does — a hybrid shell, a micro-frontend, a widget embedded in somebody else's page.
- For a horizontal header, use Mega Menu; for in-page sections, use Anchor. This is the persistent side rail.
Async states
None — this component has nothing to load, so it has no loading, empty or error state to drive.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| menuGroups | readonly MenuGroup[] | [] | Grouped navigation items. |
| activeRoute | string | '' | The path the reader is on, so one item can be marked current. Controlled rather than derived. See the class note: matching a route is the router's job, and a component that guessed at it would be wrong in a different way in every app. `[activeRoute]="router.url"` is the whole wiring for a routed app. |
| childKey | string | null | 'c' | URL parameter naming the open sub-item — the `?c=…` `sv-sidebar` has always used. Read through `SvNavLocation`, so it is inert until the app provides one. `null` turns sub-item tracking off. |
| activeChildId | string | null | null | Pin the open sub-item, ignoring the URL. For a host that keeps this in its own state rather than in the address. |
| collapsed | boolean (two-way) | false | Icon-only rail. Two-way via `[(collapsed)]`. |
| mobileOpen | boolean (two-way) | false | Mobile drawer open. Two-way via `[(mobileOpen)]`. |
| mobileBreakpoint | number | 1024 | Viewport width below which the sidebar becomes an off-canvas drawer driven by `mobileOpen`. Set to `0` to keep it docked at every size — e.g. when it is embedded in a panel rather than used as the page's shell. |
| ariaLabel | string | 'Main navigation' | Accessible name of the `<nav>` landmark. A page with two of these — a primary sidebar and a secondary panel — needs them told apart. |
| showHeader | boolean | true | Render the header / footer slots at all. A rail with no chrome is a common shape and an empty bordered box is not. |
| showFooter | boolean | true | Render the footer area holding the `[slot=footer]` content and, outside the mobile drawer, the collapse toggle. |
| surfaceId | string | null | null | Names this sidebar in the shared navigation-analytics stream. Ignored unless `provideSvNavTelemetry()` is wired up. |
| customClass | string | '' | Additional CSS classes on the `<aside>`. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| itemSelected | SidebarNavSelection | Something was picked. Carries the group and, for a sub-item, the child — a host closing a drawer needs the event, and a host doing its own analytics needs to know *which* thing was clicked, not just that one was. |
| mobileClose | void | The drawer should close. Emitted alongside `mobileOpen` being set to `false`, for a host that has its own shell state to keep in step. |
| toggleCollapse | boolean | The rail toggle was pressed. `collapsed` is already flipped. |
| collapsedChange | boolean | Emits the new `collapsed` whenever it changes — the output half of `[(collapsed)]`. |
| mobileOpenChange | boolean | Emits the new `mobileOpen` whenever it changes — the output half of `[(mobileOpen)]`. |
Customization & theming
- Expanded, collapsed rail and mobile drawer out of one model — `N-2` Bottom Navigation and `N-3` Rail reuse the exported model and matcher rather than re-implementing route sync.
- Badges per item, with four tints; sub-navigation groups per item.
- `childKey` names the URL parameter that tracks the open sub-item, read through `SvNavLocation`; `activeChildId` pins it from host state instead.
- `showHeader` / `showFooter` remove the chrome boxes entirely for a bare rail.
Best practices
- Do: Bind `activeRoute` to your router's current URL. The component deliberately does not derive it: matching a route is the router's hardest job, and a component that guessed would be wrong differently in every app.
- Do: Project your own header and footer. The component ships no logo, no wordmark and no resource links — that welded-in chrome is exactly what made the previous `sv-sidebar` unusable outside this documentation site.
- Tip: Give the `<nav>` a distinct `ariaLabel` when a page has two of them. A primary sidebar and a secondary settings panel are two landmarks, and a screen-reader user needs them told apart.
- Don't: Don't keep sub-navigation open in the collapsed rail. There is no room for it, and a tooltip is not a sub-menu.
Accessibility
- Every destination is a real `<a href>`, so middle-click, ⌘-click and "copy link address" work — which they cannot when a router directive owns the element.
- The current item carries `aria-current="page"`, as does the open sub-item.
- The collapse control is a real `<button>` with `aria-expanded` and a label that says which way it will go.
- A modified click is never intercepted: that gesture means "open this somewhere else".
- The `<nav>` landmark takes its accessible name from `ariaLabel`.
Feedback on Sidebar Nav
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.