Page Heading

The <smart-page-heading> component provides a composable page heading region with optional slots for breadcrumbs, banner image, avatar, logo, title, subtitle, meta, stats, actions, and filters. It is independent of <smart-page> and can be used standalone or inside any layout. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract PageHeadingBaseComponent defines the shared API — optional IPageHeadingOptions and cssClass (alias class). PageHeadingStandardComponent is a barebones placeholder concrete implementation. PageHeadingComponent is the public wrapper that renders PageHeadingStandardComponent by default and accepts a custom replacement via PAGE_HEADING_STANDARD_COMPONENT_TOKEN.


Usage

<smart-page-heading [options]="options()" />

<ng-template #actionsTpl>
  <button type="button" (click)="onAction('edit')">Edit</button>
  <button type="button" (click)="onAction('publish')">Publish</button>
</ng-template>

Components

PageHeadingComponent (<smart-page-heading>)

Main wrapper component. Renders PageHeadingStandardComponent by default. When PAGE_HEADING_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.

PageHeadingStandardComponent (<smart-page-heading-standard>)

Barebones placeholder concrete implementation. Always renders a wrapper <div> and a <header>. Renders any of the optional slots (breadcrumbsTpl, bannerTpl, avatarTpl, logoTpl, metaTpl, statsTpl, actionsTpl, filtersTpl) only when provided. Renders <h1> with options.title and <p class="subtitle"> with options.subtitle only when those strings are non-empty. 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.

PageHeadingBaseComponent (abstract)

Abstract base directive for extending custom page-heading implementations. Exposes options as an InputSignal<IPageHeadingOptions | undefined> and cssClass as an InputSignal<string> (with alias class).

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IPageHeadingOptions | undefined>-Optional configuration (title, subtitle, slot templates)
classInputSignal<string>''External CSS classes (alias for cssClass)

IPageHeadingOptions

All properties are optional. The default PageHeadingStandardComponent consumes every property; a section is rendered only when its template/string is provided.

PAGE_HEADING_STANDARD_COMPONENT_TOKEN

InjectionToken that allows replacing the default PageHeadingStandardComponent with a custom implementation. Provide a Type<PageHeadingBaseComponent> to override.

Extending the base class

import { NgTemplateOutlet } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  input,
  TemplateRef,
  viewChild,
  ViewEncapsulation,
} from '@angular/core';

import {
  IPageHeadingOptions,
  PAGE_HEADING_STANDARD_COMPONENT_TOKEN,
  PageHeadingBaseComponent,
  PageHeadingComponent,
} from '@smartsoft001/angular';

/**
 * A custom page heading built on `PageHeadingBaseComponent`.
 *
 * The base contributes the `options` and `class` inputs; the implementation
 * decides which `TemplateRef` slots of `IPageHeadingOptions` it renders and
 * in which order.
 */
@Component({
  selector: 'docs-custom-page-heading',
  template: `
    <div [class]="containerClasses()">
      @if (options()?.breadcrumbsTpl) {
        <nav class="docs-page-heading__breadcrumbs" aria-label="Breadcrumb">
          <ng-container [ngTemplateOutlet]="options()!.breadcrumbsTpl!" />
        </nav>
      }

      <header class="docs-page-heading__header">
        <div>
          @if (options()?.title) {
            <h1 class="docs-page-heading__title">{{ options()!.title }}</h1>
          }
          @if (options()?.subtitle) {
            <p class="docs-page-heading__subtitle">{{ options()!.subtitle }}</p>
          }
          @if (options()?.metaTpl) {
            <div class="docs-page-heading__meta">
              <ng-container [ngTemplateOutlet]="options()!.metaTpl!" />
            </div>
          }
        </div>

        @if (options()?.actionsTpl) {
          <div class="docs-page-heading__actions">
            <ng-container [ngTemplateOutlet]="options()!.actionsTpl!" />
          </div>
        }
      </header>
    </div>
  `,
  imports: [NgTemplateOutlet],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomPageHeadingComponent extends PageHeadingBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
  override cssClass = input<string>('');

  containerClasses = computed(() => {
    const classes = ['docs-page-heading'];
    const layout = this.options()?.presentation?.layout;
    if (layout) classes.push(`docs-page-heading--${layout}`);
    const extra = this.cssClass();
    if (extra) classes.push(extra);
    return classes.join(' ');
  });
}

/**
 * Registering the implementation against
 * `PAGE_HEADING_STANDARD_COMPONENT_TOKEN` makes every `<smart-page-heading>`
 * in this injector render it instead of the standard variation. The slots are
 * `<ng-template>` references owned by this host, because
 * `IPageHeadingOptions` takes `TemplateRef`s.
 */
@Component({
  selector: 'docs-page-heading-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [PageHeadingComponent],
  providers: [
    {
      provide: PAGE_HEADING_STANDARD_COMPONENT_TOKEN,
      useValue: CustomPageHeadingComponent,
    },
  ],
  template: `
    <smart-page-heading [options]="options()" />

    <ng-template #breadcrumbsTpl>
      <a href="#">Jobs</a>
      <span aria-hidden="true">/</span>
      <span>Engineering</span>
    </ng-template>

    <ng-template #metaTpl>
      <span>Remote</span>
      <span>$120k - $140k</span>
    </ng-template>

    <ng-template #actionsTpl>
      <button type="button">Edit</button>
      <button type="button">Publish</button>
    </ng-template>
  `,
})
export class PageHeadingCustomExampleComponent {
  breadcrumbsTpl = viewChild<TemplateRef<unknown>>('breadcrumbsTpl');
  metaTpl = viewChild<TemplateRef<unknown>>('metaTpl');
  actionsTpl = viewChild<TemplateRef<unknown>>('actionsTpl');

  options = computed<IPageHeadingOptions>(() => ({
    title: 'Back End Developer',
    subtitle: 'Full-time, Engineering',
    breadcrumbsTpl: this.breadcrumbsTpl(),
    metaTpl: this.metaTpl(),
    actionsTpl: this.actionsTpl(),
    presentation: { layout: 'links-right' },
  }));
}

HyperUI preset

PageHeadingPresetComponent (smart-page-heading-preset) is a HyperUI-styled variation. Unlike the standard page-heading (a page title block), this preset intentionally renders a NAVBAR look: a sticky-ready <header> bar with a logo/brand zone, a responsive desktop nav zone, a CTA/actions zone (or a user avatar zone), and a mobile hamburger that toggles a collapsible panel. The collapse is driven by a local menuOpened signal — no external JS runtime.

Register it through PAGE_HEADING_STANDARD_COMPONENT_TOKEN to restyle every <smart-page-heading>, or use <smart-page-heading-preset> directly.

Layouts (presentation.layout)

LayoutArrangement
links-leftLogo, then nav directly after it, CTAs pushed right (default)
links-centerLogo left, nav centered, CTAs right
links-rightLogo left (flex-1), nav + CTAs grouped on the right
userLike links-right, but an avatarTpl zone instead of CTAs

New IPageHeadingOptions fields

  • navTpl?: TemplateRef<unknown> — desktop nav content (hidden below md, repeated inside the mobile panel when open). Preset-only.
  • presentation?: { layout?: 'links-left' | 'links-center' | 'links-right' | 'user' } — selects the navbar arrangement (default links-left). Preset-only.

Existing logoTpl (with a title string fallback), actionsTpl, and avatarTpl (used by the user layout) are reused for the brand, CTA, and user zones respectively.

Example nav recipe

The preset renders only the nav zone wrapper; supply the list via navTpl:

Accents stay teal-600 (light) / teal-300 (dark) per the source template.

Out of scope: avatar dropdown open/close logic — the user supplies the full interactive avatar/menu markup via avatarTpl; the preset only renders the zone (hidden md:relative md:block).

Source

The component lives in packages/shared/angular/src/lib/components/page-heading and is documented for Claude Code by the angular-components-page-heading skill.