Section Heading

The <smart-section-heading> component provides a heading region for sections within a page (positioned between <smart-page-heading> and <smart-card-heading> in size and scope), with optional slots for label, description, actions, tabs, an input group (search), and a badge. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract SectionHeadingBaseComponent defines the shared API — optional ISectionHeadingOptions and cssClass (alias class). SectionHeadingStandardComponent is a barebones placeholder concrete implementation. SectionHeadingComponent is the public wrapper that renders SectionHeadingStandardComponent by default and accepts a custom replacement via SECTION_HEADING_STANDARD_COMPONENT_TOKEN.


Usage

<ng-template #actions>
  <button type="button" (click)="onInvite()">Invite member</button>
</ng-template>

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

Components

SectionHeadingComponent (<smart-section-heading>)

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

SectionHeadingStandardComponent (<smart-section-heading-standard>)

Barebones placeholder concrete implementation. Renders a wrapper <div> with a header row containing <h3> (title) with optional inline <span class="label">, a description paragraph, and the badge/inputGroup/actions slot on the right. A separate tabs row sits beneath the header. Each section is rendered only when its corresponding template/string is provided. 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.

SectionHeadingBaseComponent (abstract)

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

API

Inputs

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

ISectionHeadingOptions

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

SECTION_HEADING_STANDARD_COMPONENT_TOKEN

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

Extending the base class

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

import {
  ISectionHeadingOptions,
  SECTION_HEADING_STANDARD_COMPONENT_TOKEN,
  SectionHeadingBaseComponent,
  SectionHeadingComponent,
} from '@smartsoft001/angular';

@Component({
  selector: 'docs-custom-section-heading',
  template: `
    <div [class]="containerClasses()">
      <div class="docs-section-heading__text">
        @if (options()?.label) {
          <p class="docs-section-heading__label">{{ options()?.label }}</p>
        }

        @if (options()?.title) {
          <h2 class="docs-section-heading__title">{{ options()?.title }}</h2>
        }

        @if (options()?.description) {
          <p class="docs-section-heading__description">
            {{ options()?.description }}
          </p>
        }
      </div>

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

  // Templates travel inside `options`, which is a plain object, so slots keep
  // working even though NgComponentOutlet drops projected content.
  readonly actionsTpl = computed(
    () => this.options()?.actionsTpl as TemplateRef<unknown>,
  );

  readonly containerClasses = computed(() => {
    const classes = ['docs-section-heading'];
    const extra = this.cssClass();
    if (extra) classes.push(extra);
    return classes.join(' ');
  });
}

@Component({
  selector: 'docs-section-heading-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [SectionHeadingComponent],
  // The token swaps the standard heading for the custom one everywhere below
  // this component, so consumers keep writing `<smart-section-heading>`.
  providers: [
    {
      provide: SECTION_HEADING_STANDARD_COMPONENT_TOKEN,
      useValue: CustomSectionHeadingComponent,
    },
  ],
  template: `
    <ng-template #actions>
      <a href="#" class="docs-section-heading__cta">Get started</a>
    </ng-template>

    <smart-section-heading [options]="buildOptions(actions)" />
  `,
})
export class SectionHeadingCustomExampleComponent {
  // A factory because an Angular template expression cannot spread the
  // TemplateRef declared above into an object literal.
  buildOptions(actions: TemplateRef<unknown>): ISectionHeadingOptions {
    return {
      label: 'New',
      title: 'Manage your team in one place',
      description:
        'A balanced two-column split of copy and imagery for the default layout.',
      actionsTpl: actions,
    };
  }
}

HyperUI preset

SectionHeadingPresetComponent (smart-section-heading-preset) is a HyperUI-styled "content with image" variation. It renders an eyebrow row (label + badgeTpl), an <h2> title, a description <p>, actionsTpl below the text, and an optional image column fed by imageTpl. Vanilla Tailwind classes are prefixed with smart: and ship explicit smart:dark:* variants (gray-900 ↔ white for the title, gray-700 ↔ gray-300 for text). Every zone exposes a data-role hook: section, grid, text, image, eyebrow, actions.

New ISectionHeadingOptions fields

  • imageTpl?: TemplateRef<unknown> — the image column. The zone is only rendered when this is provided (@if); the template owns the <img> and its classes.
  • presentation?: { layout?: 'half' | 'narrow' | 'wide' | 'vertical' } — layout selector (default half). Consumed only by the preset; the standard component ignores it.
LayoutGridNotes
halfmd:grid-cols-2, text | imageDefault. Balanced two-column split.
narrowmd:grid-cols-4, text (1) | image (3)Narrow copy, wide image.
widemd:grid-cols-4, image (3) | text (1)Image rendered first.
verticalspace-y-* stack (no grid)Copy on top, image underneath.

Register the preset

Registering the token restyles every <smart-section-heading>; alternatively use <smart-section-heading-preset> directly.

Gaps

tabsTpl and inputGroupTpl are not styled by this preset (they are only rendered by the standard component). Use the standard component when those slots are needed.

Source

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