Multi-Column Layout
The <smart-multi-column-layout> component provides a three-column application shell — a left navigation <aside>, a main content region, and a right secondary <aside>, with an optional top header. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract MultiColumnLayoutBaseComponent defines the shared API — optional IMultiColumnLayoutOptions and cssClass (alias class). MultiColumnLayoutStandardComponent is a barebones placeholder concrete implementation. MultiColumnLayoutComponent is the public wrapper that renders MultiColumnLayoutStandardComponent by default and accepts a custom replacement via MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN.
Usage
<smart-multi-column-layout [options]="options()">
<h2>Quarterly report is ready</h2>
<p>The Q3 numbers are in. Open the attachment for the full breakdown.</p>
</smart-multi-column-layout>
<ng-template #headerTpl>
<strong>Inbox</strong>
</ng-template>
<ng-template #navTpl>
<a href="/inbox">Inbox</a>
<a href="/drafts">Drafts</a>
<a href="/sent">Sent</a>
</ng-template>
<ng-template #secondaryTpl>
<span>Storage: 4.2 GB of 15 GB used</span>
</ng-template>
Components
MultiColumnLayoutComponent (<smart-multi-column-layout>)
Main wrapper component. Renders MultiColumnLayoutStandardComponent by default. When MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Accepts main content via projected <ng-content>.
MultiColumnLayoutStandardComponent (<smart-multi-column-layout-standard>)
Barebones placeholder concrete implementation. Renders a wrapper <div> containing an optional <header> (when options.headerTpl is provided), an <aside class="nav">, a <main> element that projects <ng-content>, and an <aside class="secondary">. 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.
MultiColumnLayoutBaseComponent (abstract)
Abstract base directive for extending custom multi-column-layout implementations. Exposes options as an InputSignal<IMultiColumnLayoutOptions | undefined> and cssClass as an InputSignal<string> (with alias class).
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IMultiColumnLayoutOptions | undefined> | - | Optional configuration (nav, secondary, header, width) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
IMultiColumnLayoutOptions
The default MultiColumnLayoutStandardComponent consumes navTpl, secondaryTpl, and headerTpl. title, width, and secondaryWidth are reserved for custom implementations registered via MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN.
MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default MultiColumnLayoutStandardComponent with a custom implementation. Provide a Type<MultiColumnLayoutBaseComponent> to override.
Extending the base class
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
TemplateRef,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import {
IMultiColumnLayoutOptions,
MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN,
MultiColumnLayoutBaseComponent,
MultiColumnLayoutComponent,
} from '@smartsoft001/angular';
/**
* A custom multi column layout built on `MultiColumnLayoutBaseComponent`.
*
* The base contributes the `options` and `class` inputs; the implementation
* decides the column order and where each `TemplateRef` slot of
* `IMultiColumnLayoutOptions` is projected.
*/
@Component({
selector: 'docs-custom-multi-column-layout',
template: `
<div [class]="containerClasses()">
@if (options()?.title) {
<h1 class="docs-multi-column-layout__title">{{ options()!.title }}</h1>
}
@if (options()?.headerTpl) {
<header class="docs-multi-column-layout__header">
<ng-container [ngTemplateOutlet]="options()!.headerTpl!" />
</header>
}
@if (options()?.navTpl) {
<aside class="docs-multi-column-layout__nav">
<ng-container [ngTemplateOutlet]="options()!.navTpl!" />
</aside>
}
<!--
smart-multi-column-layout renders a custom implementation through
NgComponentOutlet, which does not forward projected content. Only
options and cssClass arrive here, so the main column is owned by the
implementation instead of relying on ng-content.
-->
<main class="docs-multi-column-layout__main">
<p>Three unread conversations, oldest from Tuesday.</p>
</main>
@if (options()?.secondaryTpl) {
<aside class="docs-multi-column-layout__secondary">
<ng-container [ngTemplateOutlet]="options()!.secondaryTpl!" />
</aside>
}
</div>
`,
imports: [NgTemplateOutlet],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomMultiColumnLayoutComponent extends MultiColumnLayoutBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() => {
const classes = ['docs-multi-column-layout'];
const width = this.options()?.width;
if (width) classes.push(`docs-multi-column-layout--${width}`);
const secondaryWidth = this.options()?.secondaryWidth;
if (secondaryWidth) {
classes.push(`docs-multi-column-layout--secondary-${secondaryWidth}`);
}
const extra = this.cssClass();
if (extra) classes.push(extra);
return classes.join(' ');
});
}
/**
* Registering the implementation against
* `MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN` makes every
* `<smart-multi-column-layout>` in this injector render it instead of the
* standard variation. The slots are `<ng-template>` references owned by this
* host, because `IMultiColumnLayoutOptions` takes `TemplateRef`s.
*/
@Component({
selector: 'docs-multi-column-layout-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MultiColumnLayoutComponent],
providers: [
{
provide: MULTI_COLUMN_LAYOUT_STANDARD_COMPONENT_TOKEN,
useValue: CustomMultiColumnLayoutComponent,
},
],
template: `
<smart-multi-column-layout [options]="options()" />
<ng-template #headerTpl>
<span>Unread first</span>
</ng-template>
<ng-template #navTpl>
<a href="#">Inbox</a>
<a href="#">Drafts</a>
<a href="#">Sent</a>
</ng-template>
<ng-template #secondaryTpl>
<span>Storage: 4.2 GB of 15 GB used</span>
</ng-template>
`,
})
export class MultiColumnLayoutCustomExampleComponent {
headerTpl = viewChild<TemplateRef<unknown>>('headerTpl');
navTpl = viewChild<TemplateRef<unknown>>('navTpl');
secondaryTpl = viewChild<TemplateRef<unknown>>('secondaryTpl');
options = computed<IMultiColumnLayoutOptions>(() => ({
title: 'Inbox',
width: 'full',
secondaryWidth: 'sm',
headerTpl: this.headerTpl(),
navTpl: this.navTpl(),
secondaryTpl: this.secondaryTpl(),
}));
}
Preset
MultiColumnLayoutPresetComponent (<smart-multi-column-layout-preset>) is a fully styled drop-in replacement for the barebones standard component. It extends MultiColumnLayoutStandardComponent and renders a Tailwind app-shell scaffold with explicit light/dark classes (every utility is smart:-prefixed).
Structure
- root (
data-role="root") — full-height gray page (smart:min-h-full smart:bg-gray-50 smart:dark:bg-gray-900); merges the externalcssClass. - header (
data-role="header") — rendered only whenoptions.headerTploroptions.titleis set. White surface with a bottom border, matching the stacked-layout/sidebar-layout preset headers. RendersheaderTpl, falling back to atitle<h1>(data-role="title"). - flex row containing:
- nav (
data-role="nav",<aside>) — rendered only whenoptions.navTplis set. White surface,smart:w-64 smart:shrink-0 smart:border-e, mirroring the sidebar-layout preset nav. - content (
data-role="content",<main>) — gray page surface withsmart:py-8; projects<ng-content>inside an inner container whose horizontal rhythm followsoptions.width. - secondary (
data-role="secondary",<aside>) — rendered only whenoptions.secondaryTplis set. White surface on the trailing edge (smart:border-s); width followsoptions.secondaryWidth.
- nav (
Options consumed
The preset consumes the full IMultiColumnLayoutOptions surface (the standard component only reads the templates):
width—'constrained'centers the main content container atmax-w-7xl;'full'(default) uses full width. Both keeppx-4 sm:px-6 lg:px-8.secondaryWidth—'sm'→w-64(default),'md'→w-80,'lg'→w-96.headerTpl/title,navTpl,secondaryTpl— as above.
Class recipes live in preset/preset-classes.util.ts (getMultiColumnLayout…Classes, including the width/secondaryWidth maps).
Token registration
When registered through the token, the wrapper passes inputs by canonical name via NgComponentOutlet, so the preset overrides cssClass as input<string>('') (dropping the class alias).
Gaps
- The nav and secondary asides render on the flex row at all viewport sizes — there is no built-in mobile collapse/drawer behavior.
- Files:
preset/preset.component.ts,preset/preset.component.html,preset/preset-classes.util.ts; storyPresetinmulti-column-layout.component.stories.ts.
Source
The component lives in packages/shared/angular/src/lib/components/multi-column-layout and is documented for Claude Code by the angular-components-multi-column-layout skill.