Stacked Layout
The <smart-stacked-layout> component provides a top-to-bottom application shell with a top navigation bar, an optional page header section, and a main content region. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract StackedLayoutBaseComponent defines the shared API — optional IStackedLayoutOptions and cssClass (alias class). StackedLayoutStandardComponent is a barebones placeholder concrete implementation. StackedLayoutComponent is the public wrapper that renders StackedLayoutStandardComponent by default and accepts a custom replacement via STACKED_LAYOUT_STANDARD_COMPONENT_TOKEN.
Usage
<ng-template #navTpl>
<a href="#dashboard">Dashboard</a>
<a href="#projects">Projects</a>
<a href="#reports">Reports</a>
</ng-template>
<smart-stacked-layout [options]="options()">
<p>You have 3 active projects this sprint.</p>
</smart-stacked-layout>
Components
StackedLayoutComponent (<smart-stacked-layout>)
Main wrapper component. Renders StackedLayoutStandardComponent by default. When STACKED_LAYOUT_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Accepts main content via projected <ng-content>.
StackedLayoutStandardComponent (<smart-stacked-layout-standard>)
Barebones native-HTML implementation. Renders a wrapper <div data-container-width="…"> with a top <header> containing a <nav>, an optional second <header> for the page heading section (options.headerTpl when provided, otherwise <h1 data-role="title">{{ options.title }}</h1> when options.title is set), and a <main> element that projects <ng-content>. The external cssClass is applied to the wrapper. It does not include Tailwind UI styling.
StackedLayoutBaseComponent (abstract)
Abstract base directive for extending custom stacked-layout implementations. Exposes options as an InputSignal<IStackedLayoutOptions | undefined> and cssClass as an InputSignal<string> (with alias class).
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IStackedLayoutOptions | undefined> | - | Optional configuration (title, navTpl, headerTpl, container) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
IStackedLayoutOptions
The default StackedLayoutStandardComponent consumes navTpl (rendered inside the top <nav>), headerTpl (rendered as a secondary <header> block beneath the navigation) and title (rendered as <header><h1 data-role="title"> when there is no headerTpl, the same precedence as the preset). containerWidth is purely visual: the standard only exposes it as data-container-width on the root (default 'xl'), and the max-width is styled by StackedLayoutPresetComponent.
STACKED_LAYOUT_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default StackedLayoutStandardComponent with a custom implementation. Provide a Type<StackedLayoutBaseComponent> to override.
Extending the base class
import { NgTemplateOutlet } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
TemplateRef,
viewChild,
ViewEncapsulation,
} from '@angular/core';
import {
IStackedLayoutOptions,
STACKED_LAYOUT_STANDARD_COMPONENT_TOKEN,
StackedLayoutBaseComponent,
StackedLayoutComponent,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-stacked-layout',
template: `
<div [class]="containerClasses()">
<header class="docs-stacked-layout__nav">
@if (options()?.navTpl) {
<nav>
<ng-container [ngTemplateOutlet]="options()!.navTpl!" />
</nav>
}
</header>
@if (options()?.headerTpl) {
<header class="docs-stacked-layout__header">
<ng-container [ngTemplateOutlet]="options()!.headerTpl!" />
</header>
} @else if (options()?.title) {
<header class="docs-stacked-layout__header">
<h1>{{ options()!.title }}</h1>
</header>
}
<!--
smart-stacked-layout renders a custom implementation through
NgComponentOutlet, which forwards neither projected content nor extra
inputs. Only options and cssClass arrive here, so the page body comes
from options().* templates instead of <ng-content>.
-->
<main class="docs-stacked-layout__main">
<p>Main content rendered by the layout implementation.</p>
</main>
</div>
`,
imports: [NgTemplateOutlet],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomStackedLayoutComponent extends StackedLayoutBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() =>
[
'docs-stacked-layout',
`docs-stacked-layout--${this.options()?.containerWidth ?? 'full'}`,
this.cssClass(),
]
.filter(Boolean)
.join(' '),
);
}
@Component({
selector: 'docs-stacked-layout-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [StackedLayoutComponent],
// The token swaps the standard layout for the custom one everywhere below
// this component, so consumers keep writing `<smart-stacked-layout>`.
providers: [
{
provide: STACKED_LAYOUT_STANDARD_COMPONENT_TOKEN,
useValue: CustomStackedLayoutComponent,
},
],
template: `
<ng-template #navTpl>
<a href="#dashboard">Dashboard</a>
<a href="#team">Team</a>
<a href="#projects">Projects</a>
</ng-template>
<ng-template #headerTpl>
<h1>Projects</h1>
</ng-template>
<smart-stacked-layout [options]="options()" />
`,
})
export class StackedLayoutCustomExampleComponent {
private navTpl = viewChild.required<TemplateRef<unknown>>('navTpl');
private headerTpl = viewChild.required<TemplateRef<unknown>>('headerTpl');
// Both slots of IStackedLayoutOptions are TemplateRefs, so they are read from
// the host view. computed() keeps the object identity stable between checks.
options = computed<IStackedLayoutOptions>(() => ({
title: 'Projects',
containerWidth: 'xl',
navTpl: this.navTpl(),
headerTpl: this.headerTpl(),
}));
}
HyperUI preset
StackedLayoutPresetComponent (<smart-stacked-layout-preset>) is the HyperUI-styled drop-in for StackedLayoutStandardComponent. It uses the same IStackedLayoutOptions API — no interface changes, no new options fields — and delivers the page scaffold in the HyperUI container rhythm:
- Page root (
data-role="root"): full-height gray surface —smart:min-h-full smart:bg-gray-50 smart:dark:bg-gray-900. The externalcssClassis merged here. - Header zone: white bar (
smart:bg-white smart:shadow-sm smart:dark:bg-gray-800 smart:dark:border-b smart:dark:border-gray-700) whose inner container (data-role="header",smart:py-4) rendersnavTpl(data-role="nav"), thenheaderTplif present, elsetitleas an<h1 data-role="title">fallback. - Main content (
data-role="content",smart:py-8): the container wraps a bordered content card (smart:rounded-lg smart:border smart:bg-white smart:p-4 smart:shadow-sm smart:sm:p-6 smart:dark:bg-gray-800) that projects<ng-content>— identical projection semantics to the standard component.
containerWidth mapping
Both the header and content containers share the base smart:mx-auto smart:px-4 smart:sm:px-6 smart:lg:px-8 plus a max-width from options.containerWidth:
| containerWidth | max-width class |
|---|---|
sm | smart:max-w-3xl |
md | smart:max-w-5xl |
lg | smart:max-w-6xl |
xl (default/unset) | smart:max-w-7xl |
full | smart:max-w-none |
Documented gaps
The preset delivers the PAGE SCAFFOLD only. The grid "content + image" section variants from the shared FRA-206 task description are provided by the section-heading preset — use both together (section-heading blocks projected into the stacked-layout content region).
- Preset:
packages/shared/angular/src/lib/components/stacked-layout/preset/preset.component.ts - Class recipes:
packages/shared/angular/src/lib/components/stacked-layout/preset/preset-classes.util.ts
Source
The component lives in packages/shared/angular/src/lib/components/stacked-layout and is documented for Claude Code by the angular-components-stacked-layout skill.