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>(aliasclass)actionClick: OutputEmitterRef<IEmptyStateActionClick>itemClick: OutputEmitterRef<IEmptyStateItemClick>
IEmptyStateActionClick = { actionId: string } IEmptyStateItemClick = { itemId: string }
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<IEmptyStateOptions | undefined> | - | Empty state configuration |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
Outputs
| Output | Type | Description |
|---|---|---|
actionClick | OutputEmitterRef<IEmptyStateActionClick> | Emitted when a button-type action is clicked |
itemClick | OutputEmitterRef<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.