Tabs
The <smart-tabs> component renders a tabbed navigation strip (anchor-based for routing or button-based for in-page selection) with a <select> fallback for mobile. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract TabsBaseComponent defines the shared API — options (ITabsOptions), selectedId (two-way ModelSignal<string | null>), cssClass (alias class), and the tabChange output. TabsStandardComponent is a barebones placeholder using native <nav>, <ul>, <a>, <button> and <select> elements. TabsComponent is the public wrapper that renders TabsStandardComponent by default and accepts a custom replacement via TABS_STANDARD_COMPONENT_TOKEN.
Usage
<smart-tabs
[options]="options"
[(selectedId)]="selectedTab"
(tabChange)="onTabChange($event)"
/>
Components
TabsComponent (<smart-tabs>)
Main wrapper. Delegates to TabsStandardComponent by default. When TABS_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet. Re-emits tabChange and forwards two-way selectedId.
TabsStandardComponent (<smart-tabs-standard>)
Barebones placeholder using native HTML. Renders an outer wrapper with cssClass, an optional mobile <select> (rendered when options.showMobileSelect is true or unset and items is non-empty), and a <nav class="tabs-desktop"> containing a <ul class="tabs-list"> of items. Each item renders as <a class="tab-link"> when href is provided, otherwise <button class="tab-button"> emitting tabChange and updating selectedId. Adds current class and aria-current="page" when item.id === selectedId(). Supports optional iconTpl and badge per item.
TabsPresetComponent (<smart-tabs-preset>)
Styled variation that extends TabsBaseComponent and is a drop-in replacement for TabsStandardComponent. Register it via TABS_STANDARD_COMPONENT_TOKEN to restyle every <smart-tabs>, or use the <smart-tabs-preset> selector directly. It renders the translated Preline tab nav for every SmartTabsLayout (underline, underline-with-icons, underline-with-badges, underline-full-width, pills, pills-on-gray, pills-with-brand-color, bar-with-underline, simple), selected through options.layout (default 'underline'). Honors options.items (rendering <a role="tab"> when href is set, otherwise <button role="tab">), options.ariaLabel, per-item iconTpl and badge, and options.showMobileSelect (default true → renders a <select> shown only below sm and hides the nav below sm). The active tab is driven by the selectedId model (falling back to the first item) via (click) — Preline's Tabs JS plugin is not used. ARIA (role=tablist/tab, aria-selected, aria-controls, aria-orientation) is kept on the markup. All classes are smart:-prefixed Tailwind with explicit dark: variants; the per-layout class recipes live in preset/preset-classes.util.ts.
Because
TabsComponentrenders injected components viaNgComponentOutlet(which passes inputs by canonical name),TabsPresetComponentoverridescssClassasinput<string>('')without theclassalias. Bind it as[cssClass]when using the<smart-tabs-preset>selector directly, or just passclasson<smart-tabs>(the wrapper forwards it). Note:NgComponentOutletdoes not wire outputs, sotabChangedoes not bubble through<smart-tabs>when the preset is registered via the token; subscribe to it on<smart-tabs-preset>directly if you need the event.
TabsBaseComponent (abstract)
Abstract base directive. Exposes:
options: InputSignal<ITabsOptions | undefined>selectedId: ModelSignal<string | null>(defaultnull)cssClass: InputSignal<string>(aliasclass)tabChange: OutputEmitterRef<ITabChange>
ITabChange = { tabId: string }.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
options | InputSignal<ITabsOptions | undefined> | - | Tabs configuration |
selectedId | ModelSignal<string | null> | null | Two-way bindable currently selected tab id |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
Outputs
| Output | Type | Description |
|---|---|---|
tabChange | OutputEmitterRef<ITabChange> | Emitted when a button-type tab is clicked |
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
input,
ViewEncapsulation,
} from '@angular/core';
import {
ITabsOptions,
TABS_STANDARD_COMPONENT_TOKEN,
TabsBaseComponent,
TabsComponent,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-tabs',
template: `
<nav
[class]="containerClasses()"
[attr.aria-label]="options()?.ariaLabel ?? 'Tabs'"
>
@for (item of options()?.items ?? []; track item.id) {
<button
type="button"
[class]="tabClasses(item.id)"
[attr.aria-current]="isCurrent(item.id) ? 'page' : null"
(click)="select(item.id)"
>
{{ item.label ?? item.id }}
@if (item.badge !== undefined && item.badge !== null) {
<span class="docs-tabs__badge">{{ item.badge }}</span>
}
</button>
}
</nav>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomTabsComponent extends TabsBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
containerClasses = computed(() =>
['docs-tabs', this.cssClass()].filter(Boolean).join(' '),
);
// selectedId is a model on the base: the implementation updates it and
// announces the change through the tabChange output.
select(tabId: string): void {
this.selectedId.set(tabId);
this.tabChange.emit({ tabId });
}
protected isCurrent(tabId: string): boolean {
return this.selectedId() === tabId;
}
protected tabClasses(tabId: string): string {
return this.isCurrent(tabId)
? 'docs-tabs__tab docs-tabs__tab--current'
: 'docs-tabs__tab';
}
}
@Component({
selector: 'docs-tabs-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TabsComponent],
// The token swaps the standard tabs for the custom one everywhere below this
// component, so consumers keep writing `<smart-tabs>`.
providers: [
{
provide: TABS_STANDARD_COMPONENT_TOKEN,
useValue: CustomTabsComponent,
},
],
// NgComponentOutlet forwards inputs only, so the wrapper's (tabChange) and
// (selectedIdChange) stay silent here: selection lives in the custom
// component and `selectedId` below is the initial value.
template: `<smart-tabs [options]="options" [selectedId]="selectedId" />`,
})
export class TabsCustomExampleComponent {
selectedId = 'billing';
options: ITabsOptions = {
layout: 'underline',
ariaLabel: 'Account sections',
showMobileSelect: false,
items: [
{ id: 'account', label: 'Account' },
{ id: 'billing', label: 'Billing' },
{ id: 'members', label: 'Members', badge: 12 },
],
};
}
Source
The component lives in packages/shared/angular/src/lib/components/tabs and is documented for Claude Code by the angular-components-tabs skill.