Empty State

The <smart-empty-state> component renders an empty-state placeholder with an optional centered icon, title, description, primary actions and a list of starting-points / templates / recommendations. It is typically shown when a list, page or section has no content yet (e.g. No projects, Create your first project, Add team members). It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract EmptyStateBaseComponent defines the shared API — options (IEmptyStateOptions), cssClass (alias class), and the actionClick / itemClick outputs. EmptyStateStandardComponent is a barebones placeholder using native <h3>, <p>, <a>, <button>, <ul> and <li> elements. EmptyStateComponent is the public wrapper that renders EmptyStateStandardComponent by default and accepts a custom replacement via EMPTY_STATE_STANDARD_COMPONENT_TOKEN.


Usage

<smart-empty-state [options]="options" (actionClick)="onActionClick($event)" />

Components

EmptyStateComponent (<smart-empty-state>)

Main wrapper. Delegates to EmptyStateStandardComponent by default. When EMPTY_STATE_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Re-emits actionClick and itemClick.

EmptyStateStandardComponent (<smart-empty-state-standard>)

Barebones placeholder using native HTML. Renders an outer wrapper with cssClass, a <div class="empty-state"> containing optional iconTpl (rendered via NgTemplateOutlet), <h3 class="title">{{ options.title }}</h3>, <p class="description">{{ options.description }}</p>, an optional formTpl slot, an optional row of action buttons or anchors (<button class="action variant-{variant}"> or <a> when action.href provided), an optional list of items (<a class="item-link"> when item.href provided, otherwise <button class="item-button">) with title / description / meta / icon / image, and an optional footer link.

EmptyStatePresetComponent (<smart-empty-state-preset>)

Fully-styled drop-in replacement for EmptyStateStandardComponent, adapting Preline's "Invoice Table Empty State" centered block: a rounded icon tile (iconTpl), title, description, a "Learn more" text link (footerLinkLabel / footerLinkHref) and a row of action buttons. Optional items render as a simple bordered list so it stays a full drop-in. All Tailwind classes are smart:-prefixed with explicit dark: variants. Action variant maps to: primary (blue button), secondary (white/layer button), ghost (text + hover) and link (inline blue link). Register through EMPTY_STATE_STANDARD_COMPONENT_TOKEN to restyle every <smart-empty-state>, or use the selector directly. Like every preset it declares override cssClass = input<string>('') (no class alias) so NgComponentOutlet binds cssClass by its canonical name.

EmptyStateBaseComponent (abstract)

Abstract base directive. Exposes:

  • options: InputSignal<IEmptyStateOptions | undefined>
  • cssClass: InputSignal<string> (alias class)
  • actionClick: OutputEmitterRef<IEmptyStateActionClick>
  • itemClick: OutputEmitterRef<IEmptyStateItemClick>

IEmptyStateActionClick = { actionId: string } IEmptyStateItemClick = { itemId: string }

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IEmptyStateOptions | undefined>-Empty state configuration
classInputSignal<string>''External CSS classes (alias for cssClass)

Outputs

OutputTypeDescription
actionClickOutputEmitterRef<IEmptyStateActionClick>Emitted when a button-type action is clicked
itemClickOutputEmitterRef<IEmptyStateItemClick>Emitted when an item without href is clicked

Extending the base class

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

import {
  EMPTY_STATE_STANDARD_COMPONENT_TOKEN,
  EmptyStateBaseComponent,
  EmptyStateComponent,
  IEmptyStateOptions,
} from '@smartsoft001/angular';

@Component({
  selector: 'docs-custom-empty-state',
  template: `
    <div class="docs-empty-state" [class]="cssClass()">
      @if (options()?.title) {
        <h3 class="docs-empty-state__title">{{ options()?.title }}</h3>
      }
      @if (options()?.description) {
        <p class="docs-empty-state__description">
          {{ options()?.description }}
        </p>
      }
      @for (action of options()?.actions ?? []; track action.id) {
        <button
          type="button"
          class="docs-empty-state__action"
          [attr.data-variant]="action.variant ?? 'primary'"
          (click)="actionClick.emit({ actionId: action.id })"
        >
          {{ action.label }}
        </button>
      }
    </div>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomEmptyStateComponent extends EmptyStateBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class'
  // alias, so an empty state registered through the token declares it
  // explicitly.
  override cssClass = input<string>('');
}

@Component({
  selector: 'docs-empty-state-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [EmptyStateComponent],
  // The token swaps the standard empty state for the custom one everywhere
  // below this component, so consumers keep writing `<smart-empty-state>`.
  providers: [
    {
      provide: EMPTY_STATE_STANDARD_COMPONENT_TOKEN,
      useValue: CustomEmptyStateComponent,
    },
  ],
  // NgComponentOutlet forwards inputs but not outputs, so `(actionClick)` and
  // `(itemClick)` on the wrapper stay silent once a custom implementation is
  // registered - handle the click inside the custom component instead.
  template: `<smart-empty-state [options]="options" />`,
})
export class EmptyStateCustomExampleComponent {
  options: IEmptyStateOptions = {
    title: 'No draft invoices',
    description: 'Draft an invoice and send it to a customer.',
    actions: [
      { id: 'create', label: 'Create a new invoice', variant: 'primary' },
      { id: 'template', label: 'Use a template', variant: 'secondary' },
    ],
  };
}

Source

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