Vertical Navigation
The <smart-vertical-navigation> component renders a sidebar navigation with one or more groups of items. Each item can have an icon (template), an optional badge, an optional initial (used for project-style sidebars), and an href (rendered as <a>) or no href (rendered as <button> emitting itemClick). It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract VerticalNavigationBaseComponent defines the shared API — options (IVerticalNavOptions), cssClass (alias class), and the itemClick output. VerticalNavigationStandardComponent is a barebones placeholder using native <nav>, <ul>, <li>, <a> and <button> elements. VerticalNavigationComponent is the public wrapper that renders VerticalNavigationStandardComponent by default and accepts a custom replacement via VERTICAL_NAVIGATION_STANDARD_COMPONENT_TOKEN.
Usage
<smart-vertical-navigation
[options]="options"
(itemClick)="onItemClick($event)"
/>
Components
VerticalNavigationComponent (<smart-vertical-navigation>)
Main wrapper. Delegates to VerticalNavigationStandardComponent by default. When VERTICAL_NAVIGATION_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Re-emits itemClick.
VerticalNavigationStandardComponent (<smart-vertical-navigation-standard>)
Barebones placeholder using native HTML. Renders an outer wrapper with cssClass, a <nav class="vertical-navigation"> (with aria-label from options.ariaLabel or default "Sidebar"), and a <ul> of groups. Each group renders an optional <div class="group-title"> and a <ul> of items; each item renders as <a class="item-link"> (when href provided) or <button class="item-button"> (otherwise, emitting itemClick). Items get current class and aria-current="page" when item.current === true. Supports iconTpl, initial (e.g. project letter), label, and badge.
VerticalNavigationPresetComponent (<smart-vertical-navigation-preset>)
Fully-styled, drop-in concrete implementation extending VerticalNavigationBaseComponent. Renders the Preline vertical-tabs look: a container with a trailing border (border-e-2), and each item rendered as a tab (<a> for href, <button> otherwise) with its own trailing border. The current item gains the primary accent (border-blue-600, text-blue-600, font-medium); inactive items are muted gray with a primary hover/focus. Supports iconTpl, initial, label, badge, and group titles. All classes are smart:-prefixed Tailwind with explicit dark: variants. Register it through VERTICAL_NAVIGATION_STANDARD_COMPONENT_TOKEN to restyle every <smart-vertical-navigation>, or use the selector directly. Because NgComponentOutlet passes inputs by canonical name, this component overrides cssClass = input<string>('') (no class alias).
VerticalNavigationBaseComponent (abstract)
Abstract base directive. Exposes:
options: InputSignal<IVerticalNavOptions | undefined>cssClass: InputSignal<string>(aliasclass)itemClick: OutputEmitterRef<IVerticalNavItemClick>- protected
resolvedGroups: Signal<IVerticalNavGroup[]>— normalizesoptions.itemsinto a single-group structure alongsideoptions.groups
IVerticalNavItemClick = { itemId: string }.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IVerticalNavOptions | undefined> | - | Vertical navigation configuration |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
Outputs
| Output | Type | Description |
|---|---|---|
itemClick | OutputEmitterRef<IVerticalNavItemClick> | Emitted when a button-type nav item is clicked |
IVerticalNavOptions
When both items and groups are provided, items is rendered first as a leading single-group section.
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import {
IVerticalNavOptions,
VERTICAL_NAVIGATION_STANDARD_COMPONENT_TOKEN,
VerticalNavigationBaseComponent,
VerticalNavigationComponent,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-vertical-navigation',
template: `
<nav
[class]="containerClasses()"
[attr.aria-label]="options()?.ariaLabel ?? 'Sidebar'"
>
<!--
The base normalizes options.items and options.groups into one list of
groups, so the implementation only has to render groups.
-->
@for (group of groups(); track group.id ?? $index) {
<div class="docs-vertical-nav__group">
@if (group.title) {
<div class="docs-vertical-nav__group-title">{{ group.title }}</div>
}
<ul role="list">
@for (item of group.items; track item.id) {
<li [class]="itemClasses(item.current)">
@if (item.href) {
<a
[href]="item.href"
[attr.aria-current]="item.current ? 'page' : null"
>
@if (item.initial) {
<span class="docs-vertical-nav__initial">
{{ item.initial }}
</span>
}
<span>{{ item.label ?? item.id }}</span>
@if (item.badge !== undefined && item.badge !== null) {
<span class="docs-vertical-nav__badge">
{{ item.badge }}
</span>
}
</a>
} @else {
<button type="button" (click)="onItemClick(item.id)">
{{ item.label ?? item.id }}
</button>
}
</li>
}
</ul>
</div>
}
</nav>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomVerticalNavigationComponent extends VerticalNavigationBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
groups = computed(() => this.resolvedGroups());
containerClasses = computed(() =>
[
'docs-vertical-nav',
`docs-vertical-nav--${this.options()?.layout ?? 'simple'}`,
this.cssClass(),
]
.filter(Boolean)
.join(' '),
);
protected itemClasses(current: boolean | undefined): string {
return current
? 'docs-vertical-nav__item docs-vertical-nav__item--current'
: 'docs-vertical-nav__item';
}
protected onItemClick(itemId: string): void {
this.itemClick.emit({ itemId });
}
}
@Component({
selector: 'docs-vertical-navigation-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [VerticalNavigationComponent],
// The token swaps the standard navigation for the custom one everywhere below
// this component, so consumers keep writing `<smart-vertical-navigation>`.
providers: [
{
provide: VERTICAL_NAVIGATION_STANDARD_COMPONENT_TOKEN,
useValue: CustomVerticalNavigationComponent,
},
],
// NgComponentOutlet forwards inputs only, so the wrapper's (itemClick) stays
// silent: the custom implementation emits it instead.
template: `<smart-vertical-navigation [options]="options" />`,
})
export class VerticalNavigationCustomExampleComponent {
// `items` and `groups` can be combined: the base puts the loose items in a
// first, untitled group and appends the explicit groups after them.
options: IVerticalNavOptions = {
layout: 'with-badges',
ariaLabel: 'Sidebar',
items: [
{ id: 'dashboard', label: 'Dashboard', href: '#dashboard' },
{ id: 'team', label: 'Team', href: '#team', current: true, badge: 5 },
{ id: 'calendar', label: 'Calendar', href: '#calendar' },
],
groups: [
{
id: 'projects',
title: 'Projects',
items: [
{
id: 'website',
label: 'Website redesign',
href: '#website',
initial: 'W',
},
{ id: 'new-project', label: 'New project' },
],
},
],
};
}
Source
The component lives in packages/shared/angular/src/lib/components/vertical-navigation and is documented for Claude Code by the angular-components-vertical-navigation skill.