Stacked List

The <smart-stacked-list> component renders a vertical list of records with optional title, description, per-item icon/avatar, link, badge, action template, and bottom footer slot. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract StackedListBaseComponent defines the shared API — optional IStackedListOptions and cssClass (alias class). StackedListStandardComponent is a barebones placeholder concrete implementation. StackedListPresetComponent is a styled Tailwind drop-in replacement. StackedListComponent is the public wrapper that renders StackedListStandardComponent by default and accepts a custom replacement (such as the preset) via STACKED_LIST_STANDARD_COMPONENT_TOKEN.


Usage

<smart-stacked-list [options]="options" />

Components

StackedListComponent (<smart-stacked-list>)

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

StackedListStandardComponent (<smart-stacked-list-standard>)

Barebones placeholder concrete implementation. Renders a wrapper <div> containing an optional <h3 class="title">, optional <p class="description">, and a <ul role="list"> with one <li class="item"> per item. Each item renders the icon/avatar (icon template wins over avatar URL), a body group with the title (rendered as <a class="title"> if href is provided, otherwise <span class="title">) and optional description/meta, and optional badge/action template slots. When the item list is empty, the optional emptyTpl is rendered inside <div class="empty">. A bottom footerTpl renders inside <div class="footer">. The external cssClass is applied to the root wrapper. It does not include any visual styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.

StackedListPresetComponent (<smart-stacked-list-preset>)

Styled variation that extends StackedListBaseComponent and is a drop-in replacement for StackedListStandardComponent. See Preset.

StackedListBaseComponent (abstract)

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

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IStackedListOptions | undefined>-Optional configuration (title, description, items, layout flags, empty/footer slots)
classInputSignal<string>''External CSS classes (alias for cssClass)

IStackedListOptions

All properties are optional except IStackedListItem.title. The default StackedListStandardComponent renders every content property but ignores the withDividers and fullWidthOnMobile layout hints; StackedListPresetComponent honours them (see Preset). A section is rendered only when its template/string is provided. Within an item, iconTpl wins over avatarUrl when both are set.

Preset

StackedListPresetComponent (selector smart-stacked-list-preset) is the styled skin used by the Storybook stories and the docs site. It extends StackedListBaseComponent, so it takes the same options and cssClass inputs.

It renders the Tailwind UI stacked list look in smart:-prefixed Tailwind v4 classes with a smart:dark: variant on every colour:

  • Header: text-base font-semibold gray-900 / white title and a text-sm gray-500 / gray-400 description.
  • Rows: flex items-center justify-between gap-x-6 py-5. The leading media is a size-12 rounded-full avatar (avatarUrl) or a gray-100 / gray-800 rounded-full tile wrapping iconTpl (the icon template wins). The body shows the title (text-sm/6 font-semibold, rendered as a link with hover underline when href is set), a truncated text-xs/5 description and a meta line. badgeTpl and actionTpl render in a trailing group.
  • Empty state: emptyTpl renders in a dashed gray-200 / white-10 rounded-lg box when items is empty.
  • Footer: footerTpl renders under the list.
  • External class: cssClass is appended to the root wrapper.

It honours both layout hints that the standard component ignores:

OptionPreset behaviour
withDividerstrue draws divide-y gray-100 / white-10 hairlines between rows. Unset or false separates rows by spacing only.
fullWidthOnMobiletrue renders the list as a white / gray-900 card with a ring. Below sm it bleeds to the screen edge (-mx-4, square corners); from sm up it is a rounded-xl card. Rows get px-4 sm:px-6 padding.

Register it on the token to restyle every <smart-stacked-list>:

StackedListComponent renders injected components through NgComponentOutlet, which passes inputs by canonical name. For that reason StackedListPresetComponent declares cssClass as input<string>('') without the class alias. When you use the <smart-stacked-list-preset> selector directly, bind [cssClass]. On <smart-stacked-list> just pass class and the wrapper forwards it.

The class recipes live in preset/preset-classes.util.ts.

STACKED_LIST_STANDARD_COMPONENT_TOKEN

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

Extending the base class

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

import {
  IStackedListOptions,
  STACKED_LIST_STANDARD_COMPONENT_TOKEN,
  StackedListBaseComponent,
  StackedListComponent,
} from '@smartsoft001/angular';

@Component({
  selector: 'docs-custom-stacked-list',
  template: `
    <div [class]="containerClasses()">
      @if (options()?.title) {
        <h3 class="docs-stacked-list__title">{{ options()!.title }}</h3>
      }
      @if (options()?.description) {
        <p class="docs-stacked-list__description">
          {{ options()!.description }}
        </p>
      }

      <ul role="list">
        @for (item of options()?.items ?? []; track item.id ?? $index) {
          <li class="docs-stacked-list__item">
            @if (item.avatarUrl) {
              <img
                class="docs-stacked-list__avatar"
                [src]="item.avatarUrl"
                alt=""
              />
            }
            <span class="docs-stacked-list__body">
              @if (item.href) {
                <a [attr.href]="item.href">{{ item.title }}</a>
              } @else {
                <span>{{ item.title }}</span>
              }
              @if (item.description) {
                <span class="docs-stacked-list__meta">
                  {{ item.description }}
                </span>
              }
            </span>
            @if (item.meta) {
              <span class="docs-stacked-list__joined">{{ item.meta }}</span>
            }
          </li>
        }
      </ul>
    </div>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomStackedListComponent extends StackedListBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
  override cssClass = input<string>('');

  containerClasses = computed(() =>
    [
      'docs-stacked-list',
      this.options()?.withDividers ? 'docs-stacked-list--divided' : '',
      this.options()?.fullWidthOnMobile ? 'docs-stacked-list--bleed' : '',
      this.cssClass(),
    ]
      .filter(Boolean)
      .join(' '),
  );
}

@Component({
  selector: 'docs-stacked-list-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [StackedListComponent],
  // The token swaps the standard list for the custom one everywhere below this
  // component, so consumers keep writing `<smart-stacked-list>`.
  providers: [
    {
      provide: STACKED_LIST_STANDARD_COMPONENT_TOKEN,
      useValue: CustomStackedListComponent,
    },
  ],
  template: `<smart-stacked-list [options]="options" />`,
})
export class StackedListCustomExampleComponent {
  // withDividers and fullWidthOnMobile are styling hints: the standard list
  // ignores them, a custom implementation decides what they mean.
  options: IStackedListOptions = {
    title: 'Team members',
    description: 'People with access to this workspace.',
    withDividers: true,
    items: [
      {
        id: '1',
        title: 'Lindsay Walton',
        description: 'lindsay.walton@example.com',
        meta: 'Joined 12 January 2026',
      },
      {
        id: '2',
        title: 'Courtney Henry',
        description: 'courtney.henry@example.com',
        meta: 'Joined 3 February 2026',
      },
      {
        id: '3',
        title: 'Tom Cook',
        description: 'tom.cook@example.com',
        meta: 'Joined 27 February 2026',
      },
    ],
  };
}

Source

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