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

InputTypeDefaultDescription
optionsInputSignal<IStackedLayoutOptions | undefined>-Optional configuration (title, navTpl, headerTpl, container)
classInputSignal<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 external cssClass is 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) renders navTpl (data-role="nav"), then headerTpl if present, else title as 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:

containerWidthmax-width class
smsmart:max-w-3xl
mdsmart:max-w-5xl
lgsmart:max-w-6xl
xl (default/unset)smart:max-w-7xl
fullsmart: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.