Sidebar Layout
The <smart-sidebar-layout> component provides a two-column application shell with a sidebar (left or right) and a main content region, with an optional top header. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract SidebarLayoutBaseComponent defines the shared API — optional ISidebarLayoutOptions and cssClass (alias class). SidebarLayoutStandardComponent is a barebones placeholder concrete implementation. SidebarLayoutComponent is the public wrapper that renders SidebarLayoutStandardComponent by default and accepts a custom replacement via SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN.
Usage
<ng-template #sidebar>
<nav aria-label="Main">
<a href="/dashboard">Dashboard</a>
<a href="/projects">Projects</a>
<a href="/team">Team</a>
</nav>
</ng-template>
<smart-sidebar-layout [options]="options()">
<h1>Dashboard</h1>
<p>Welcome back. Here is what changed since yesterday.</p>
</smart-sidebar-layout>
Components
SidebarLayoutComponent (<smart-sidebar-layout>)
Main wrapper component. Renders SidebarLayoutStandardComponent by default. When SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Accepts main content via projected <ng-content>.
SidebarLayoutStandardComponent (<smart-sidebar-layout-standard>)
Barebones placeholder concrete implementation. Renders a wrapper <div> containing an optional <header> (when options.headerTpl is provided), an <aside> for the sidebar, and a <main> element that projects <ng-content>. The order of <aside> and <main> swaps based on options.sidebarPosition ('left' default or 'right'). The external cssClass is applied to the wrapper. It does not include Tailwind UI styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.
SidebarLayoutBaseComponent (abstract)
Abstract base directive for extending custom sidebar-layout implementations. Exposes options as an InputSignal<ISidebarLayoutOptions | undefined> and cssClass as an InputSignal<string> (with alias class).
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<ISidebarLayoutOptions | undefined> | - | Optional configuration (sidebar template, position, header) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
ISidebarLayoutOptions
The default SidebarLayoutStandardComponent consumes sidebarTpl, headerTpl, and sidebarPosition. title, mobileBreakpoint, and condensed are reserved for custom implementations registered via SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN and are ignored by SidebarLayoutStandardComponent.
SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default SidebarLayoutStandardComponent with a custom implementation. Provide a Type<SidebarLayoutBaseComponent> to override.
Extending the base class
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
TemplateRef,
ViewEncapsulation,
} from '@angular/core';
import {
ISidebarLayoutOptions,
SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN,
SidebarLayoutBaseComponent,
SidebarLayoutComponent,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-sidebar-layout',
template: `
<div
[class]="containerClasses()"
[attr.data-position]="options()?.sidebarPosition ?? 'left'"
>
<aside class="docs-sidebar-layout__sidebar">
@if (sidebarTpl()) {
<ng-container [ngTemplateOutlet]="sidebarTpl()!" />
}
</aside>
<main class="docs-sidebar-layout__main">
@if (headerTpl()) {
<header class="docs-sidebar-layout__header">
<ng-container [ngTemplateOutlet]="headerTpl()!" />
</header>
} @else if (options()?.title) {
<h1 class="docs-sidebar-layout__title">{{ options()?.title }}</h1>
}
<!--
smart-sidebar-layout renders a custom implementation through
NgComponentOutlet, which drops projected content, so <ng-content />
would stay empty here. A custom layout either renders its own body or
takes it from a template passed inside the options object.
-->
<p class="docs-sidebar-layout__body">
Main content rendered by the custom layout.
</p>
</main>
</div>
`,
imports: [NgTemplateOutlet],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomSidebarLayoutComponent extends SidebarLayoutBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
readonly sidebarTpl = computed(
() => this.options()?.sidebarTpl as TemplateRef<unknown> | undefined,
);
readonly headerTpl = computed(
() => this.options()?.headerTpl as TemplateRef<unknown> | undefined,
);
readonly containerClasses = computed(() => {
const classes = ['docs-sidebar-layout'];
if (this.options()?.condensed)
classes.push('docs-sidebar-layout--condensed');
const extra = this.cssClass();
if (extra) classes.push(extra);
return classes.join(' ');
});
}
@Component({
selector: 'docs-sidebar-layout-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SidebarLayoutComponent],
// The token swaps the standard layout for the custom one everywhere below
// this component, so consumers keep writing `<smart-sidebar-layout>`.
providers: [
{
provide: SIDEBAR_LAYOUT_STANDARD_COMPONENT_TOKEN,
useValue: CustomSidebarLayoutComponent,
},
],
template: `
<ng-template #sidebar>
<nav aria-label="Main">
<a href="#">Overview</a>
<a href="#">Team</a>
<a href="#">Projects</a>
</nav>
</ng-template>
<smart-sidebar-layout [options]="buildOptions(sidebar)" />
`,
})
export class SidebarLayoutCustomExampleComponent {
// A factory because an Angular template expression cannot spread the
// TemplateRef declared above into an object literal.
buildOptions(sidebar: TemplateRef<unknown>): ISidebarLayoutOptions {
return {
title: 'Dashboard',
sidebarTpl: sidebar,
sidebarPosition: 'left',
condensed: false,
};
}
}
Preset
SidebarLayoutPresetComponent (smart-sidebar-layout-preset) is a styled, drop-in replacement for SidebarLayoutStandardComponent. It extends the standard component (reusing isRightSidebar) and applies a Tailwind shell where every utility is smart:-prefixed with explicit smart:dark:* variants.
Structure:
- Root: full-height gray page (
smart:min-h-full smart:bg-gray-50 smart:dark:bg-gray-900);cssClassis merged onto it. - Optional header zone (
data-role="header"): rendered only whenoptions.headerTploroptions.titleis set. Styled like the stacked-layout preset header (white bg,smart:border-b, darkgray-800). Falls back to an<h1>(data-role="title") when onlytitleis given. - Row (
data-role="row"): a flex container.sidebarPosition: 'right'addssmart:flex-row-reverse. - Sidebar
<aside>(data-role="sidebar"): white,smart:shrink-0,smart:w-64(orsmart:w-16whenoptions.condensed). Border issmart:border-e(left sidebar) orsmart:border-s(right sidebar). - Content
<main>(data-role="content"): gray page surface with padding; projects<ng-content>.
No new ISidebarLayoutOptions fields — the preset consumes the existing title, headerTpl, sidebarTpl, sidebarPosition, and condensed.
Because the wrapper forwards inputs canonically through NgComponentOutlet, the preset does override cssClass = input<string>('') (drops the inherited class alias).
Class recipes live in preset/preset-classes.util.ts (getSidebarLayout*Classes); it is intentionally NOT barrel-exported.
Register it to restyle every <smart-sidebar-layout>:
Documented gap: options.mobileBreakpoint is not consumed — the standard component the preset extends does not act on it either.
Source
The component lives in packages/shared/angular/src/lib/components/sidebar-layout and is documented for Claude Code by the angular-components-sidebar-layout skill.