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> (alias class)
  • itemClick: OutputEmitterRef<IVerticalNavItemClick>
  • protected resolvedGroups: Signal<IVerticalNavGroup[]> — normalizes options.items into a single-group structure alongside options.groups

IVerticalNavItemClick = { itemId: string }.

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IVerticalNavOptions | undefined>-Vertical navigation configuration
classInputSignal<string>''External CSS classes (alias for cssClass)

Outputs

OutputTypeDescription
itemClickOutputEmitterRef<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.