Navigation
Tabs
Usage#
import { WDS_TABS } from '@wds/angular/tabs';Examples#
Default#
wds-tab and a wds-tab-panel with the same value belong together. value on wds-tabs selects a tab (the first enabled one by default); wds-change reports the value chosen by the user and the previous one.
Revenue
New users
Selected: overview
import { Component, signal } from '@angular/core';
import { WdsCardComponent } from '@wds/angular/card';
import { WDS_TABS } from '@wds/angular/tabs';
@Component({
selector: 'app-tabs-default-demo',
imports: [...WDS_TABS, WdsCardComponent],
template: `
<div class="flex w-md max-w-full flex-col gap-3">
<wds-tabs [value]="tab()" (wdsChange)="tab.set($event.value)">
<wds-tab value="overview">Overview</wds-tab>
<wds-tab value="analytics">Analytics</wds-tab>
<wds-tab value="reports">Reports</wds-tab>
<wds-tab value="settings">Settings</wds-tab>
<wds-tab-panel value="overview">
<wds-card heading="Overview" description="Key metrics from the last 30 days.">
<div class="grid grid-cols-2 gap-3">
<div><strong class="block text-2xl">$12,480</strong><p class="m-0 text-muted-foreground">Revenue</p></div>
<div><strong class="block text-2xl">+2,350</strong><p class="m-0 text-muted-foreground">New users</p></div>
</div>
</wds-card>
</wds-tab-panel>
<wds-tab-panel value="analytics">
<wds-card heading="Analytics" description="Traffic grew by 18% compared with the previous month." />
</wds-tab-panel>
<wds-tab-panel value="reports">
<wds-card heading="Reports" description="The quarterly report is ready to download." />
</wds-tab-panel>
<wds-tab-panel value="settings">
<wds-card heading="Settings" description="Manage your account preferences and notifications." />
</wds-tab-panel>
</wds-tabs>
<p class="m-0 text-sm text-muted-foreground">Selected: {{ tab() }}</p>
</div>
`,
})
export class TabsDefaultDemo {
protected readonly tab = signal('overview');
}Line#
variant="line" removes the background and underlines the selected tab.
Content of the Overview tab.
Content of the Analytics tab.
Content of the Reports tab.
import { Component } from '@angular/core';
import { WdsTabComponent, WdsTabPanelComponent, WdsTabsComponent } from '@wds/angular/tabs';
@Component({
selector: 'app-tabs-line-demo',
imports: [WdsTabsComponent, WdsTabComponent, WdsTabPanelComponent],
template: `
<wds-tabs variant="line" value="overview" class="w-full max-w-md">
<wds-tab value="overview">Overview</wds-tab>
<wds-tab value="analytics">Analytics</wds-tab>
<wds-tab value="reports">Reports</wds-tab>
<wds-tab-panel value="overview"><p class="m-0">Content of the Overview tab.</p></wds-tab-panel>
<wds-tab-panel value="analytics"><p class="m-0">Content of the Analytics tab.</p></wds-tab-panel>
<wds-tab-panel value="reports"><p class="m-0">Content of the Reports tab.</p></wds-tab-panel>
</wds-tabs>
`,
})
export class TabsLineDemo {}Vertical#
orientation="vertical" places the tab list to the left of the panels; the Up and Down arrow keys move between tabs.
import { Component } from '@angular/core';
import { WdsTabComponent, WdsTabPanelComponent, WdsTabsComponent } from '@wds/angular/tabs';
import { WdsCardComponent } from '@wds/angular/card';
import { WdsFieldComponent, WdsFieldContentComponent, WdsFieldDescriptionComponent, WdsFieldLabelComponent } from '@wds/angular/field';
import { WdsInputComponent } from '@wds/angular/input';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsSwitchComponent } from '@wds/angular/switch';
@Component({
selector: 'app-tabs-vertical-demo',
imports: [WdsTabsComponent, WdsTabComponent, WdsTabPanelComponent, WdsCardComponent, WdsFieldComponent, WdsFieldLabelComponent, WdsFieldContentComponent, WdsFieldDescriptionComponent, WdsInputComponent, WdsButtonComponent, WdsSwitchComponent],
template: `
<wds-tabs orientation="vertical" value="account" class="w-full max-w-lg">
<wds-tab value="account">Account</wds-tab>
<wds-tab value="password">Password</wds-tab>
<wds-tab value="notifications">Notifications</wds-tab>
<wds-tab-panel value="account">
<wds-card heading="Account" description="Change your account details and click Save.">
<wds-field>
<wds-field-label>Name</wds-field-label>
<wds-input value="Pedro Duarte"></wds-input>
</wds-field>
<wds-button slot="footer">Save</wds-button>
</wds-card>
</wds-tab-panel>
<wds-tab-panel value="password">
<wds-card heading="Password" description="You will be signed out after the change.">
<div class="grid gap-4">
<wds-field>
<wds-field-label>Current password</wds-field-label>
<wds-input type="password"></wds-input>
</wds-field>
<wds-field>
<wds-field-label>New password</wds-field-label>
<wds-input type="password"></wds-input>
</wds-field>
</div>
<wds-button slot="footer">Change password</wds-button>
</wds-card>
</wds-tab-panel>
<wds-tab-panel value="notifications">
<wds-card heading="Notifications">
<wds-field orientation="horizontal">
<wds-field-content>
<wds-field-label>Email notifications</wds-field-label>
<wds-field-description>A weekly summary of your activity.</wds-field-description>
</wds-field-content>
<wds-switch checked></wds-switch>
</wds-field>
</wds-card>
</wds-tab-panel>
</wds-tabs>
`,
})
export class TabsVerticalDemo {}Command#
variant="command" puts the tabs in the header of a bordered block, for example to pick a package manager. Content of the actions slot goes to the right of the tab bar, such as a copy button.
pnpm add @wds/core
npm install @wds/core
yarn add @wds/core
bun add @wds/core
import { Component, signal } from '@angular/core';
import { WdsButtonComponent } from '@wds/angular/button';
import { WDS_TABS } from '@wds/angular/tabs';
@Component({
selector: 'app-tabs-command-demo',
imports: [...WDS_TABS, WdsButtonComponent],
template: `
<wds-tabs variant="command" class="w-lg max-w-full" [value]="manager()" (wdsChange)="manager.set($event.value)">
@for (item of commands; track item.manager) {
<wds-tab [value]="item.manager">{{ item.manager }}</wds-tab>
}
<wds-button slot="actions" variant="ghost" size="icon-xs" [label]="copied() ? 'Copied' : 'Copy'" (click)="copy()">
@if (copied()) {
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
} @else {
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
}
</wds-button>
@for (item of commands; track item.manager) {
<wds-tab-panel [value]="item.manager">
<pre class="m-0 overflow-x-auto p-3 font-mono text-sm">{{ item.command }}</pre>
</wds-tab-panel>
}
</wds-tabs>
`,
})
export class TabsCommandDemo {
protected readonly commands = [
{ manager: 'pnpm', command: 'pnpm add @wds/core' },
{ manager: 'npm', command: 'npm install @wds/core' },
{ manager: 'yarn', command: 'yarn add @wds/core' },
{ manager: 'bun', command: 'bun add @wds/core' },
];
protected readonly manager = signal('pnpm');
protected readonly copied = signal(false);
protected async copy(): Promise<void> {
const item = this.commands.find((c) => c.manager === this.manager())!;
await navigator.clipboard.writeText(item.command);
this.copied.set(true);
setTimeout(() => this.copied.set(false), 1500);
}
}Disabled#
disabled on wds-tabs disables all tabs.
The “Disabled” tab cannot be clicked and the arrow keys skip it.
About us.
import { Component } from '@angular/core';
import { WdsTabComponent, WdsTabPanelComponent, WdsTabsComponent } from '@wds/angular/tabs';
@Component({
selector: 'app-tabs-disabled-demo',
imports: [WdsTabsComponent, WdsTabComponent, WdsTabPanelComponent],
template: `
<wds-tabs value="home" class="w-full max-w-md">
<wds-tab value="home">Home</wds-tab>
<wds-tab value="settings" disabled>Disabled</wds-tab>
<wds-tab value="about">About</wds-tab>
<wds-tab-panel value="home"><p class="m-0">The “Disabled” tab cannot be clicked and the arrow keys skip it.</p></wds-tab-panel>
<wds-tab-panel value="about"><p class="m-0">About us.</p></wds-tab-panel>
</wds-tabs>
`,
})
export class TabsDisabledDemo {}Manual activation#
activation="manual" the arrow keys only move focus and Enter or Space selects the tab. Use it when showing a panel is slow, such as when it loads data.
The arrow keys move focus; press Enter or Space to open a tab.
Content of the Analytics tab.
Content of the Reports tab.
import { Component } from '@angular/core';
import { WdsTabComponent, WdsTabPanelComponent, WdsTabsComponent } from '@wds/angular/tabs';
@Component({
selector: 'app-tabs-manual-demo',
imports: [WdsTabsComponent, WdsTabComponent, WdsTabPanelComponent],
template: `
<wds-tabs activation="manual" value="overview" class="w-full max-w-md">
<wds-tab value="overview">Overview</wds-tab>
<wds-tab value="analytics">Analytics</wds-tab>
<wds-tab value="reports">Reports</wds-tab>
<wds-tab-panel value="overview"><p class="m-0">The arrow keys move focus; press Enter or Space to open a tab.</p></wds-tab-panel>
<wds-tab-panel value="analytics"><p class="m-0">Content of the Analytics tab.</p></wds-tab-panel>
<wds-tab-panel value="reports"><p class="m-0">Content of the Reports tab.</p></wds-tab-panel>
</wds-tabs>
`,
})
export class TabsManualDemo {}Icons#
<svg> icon in a tab is sized and aligned with the label.
<wds-button>Button</wds-button>
import { Component } from '@angular/core';
import { WdsTabComponent, WdsTabPanelComponent, WdsTabsComponent } from '@wds/angular/tabs';
import { WdsCardComponent } from '@wds/angular/card';
import { WdsButtonComponent } from '@wds/angular/button';
@Component({
selector: 'app-tabs-icons-demo',
imports: [WdsTabsComponent, WdsTabComponent, WdsTabPanelComponent, WdsCardComponent, WdsButtonComponent],
template: `
<wds-tabs value="preview" class="w-full max-w-md">
<wds-tab value="preview">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><circle cx="9" cy="9" r="2" /><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /></svg>
Preview
</wds-tab>
<wds-tab value="code">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 18 6-6-6-6" /><path d="m8 6-6 6 6 6" /></svg>
Code
</wds-tab>
<wds-tab-panel value="preview">
<wds-card class="[&::part(content)]:grid [&::part(content)]:min-h-18 [&::part(content)]:place-items-center"><wds-button>Button</wds-button></wds-card>
</wds-tab-panel>
<wds-tab-panel value="code">
<pre class="m-0 rounded-md bg-muted p-3 text-[0.8rem]"><wds-button>Button</wds-button></pre>
</wds-tab-panel>
</wds-tabs>
`,
})
export class TabsIconsDemo {}API References#
wds-tabs#
wds-tab elements go to the tab list
and wds-tab-panel elements below it; a tab and its panel are linked by value.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| value | string | '' | value of a wds-tab). Defaults to the first enabled tab. |
| variant | WdsTabsVariant | 'default' | line: no background, the selected tab is underlined.
command: a bordered block with the tabs in its header (such as npm / yarn / pnpm / bun commands), horizontal only. |
| orientation | WdsTabsOrientation | 'horizontal' | vertical places it to the left of the panels. |
| disabled | boolean | false | |
| activation | WdsTabsActivation | 'automatic' | automatic: the arrow keys select a tab at once; manual: they only move focus and Enter or Space selects. |
Events
| Event | Description |
|---|---|
| wds-changeCustomEvent<WdsTabsChangeDetail> |
Slots
| Slot | Description |
|---|---|
| tab | wds-tab elements (assigned automatically). |
| (default) | wds-tab-panel elements. |
| actions | variant="command". |
CSS parts (::part)
| Part | Description |
|---|---|
| base | variant="command". |
| header | variant="command"). |
| list | role="tablist"). |
CSS custom properties
| Property | Description |
|---|---|
| --wds-tabs-border-color | variant="command" (default --wds-border). |
wds-tab#
wds-tabs. It moves itself to the tab list (the tab slot).
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| value | string | '' | wds-tab-panel. |
| disabled | boolean | false |
Slots
| Slot | Description |
|---|---|
| (default) | <svg> icon). |
CSS parts (::part)
| Part | Description |
|---|---|
| base |
wds-tab-panel#
value. Visible only while that tab is selected.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| value | string | '' | wds-tab value). |
Slots
| Slot | Description |
|---|---|
| (default) |
CSS parts (::part)
| Part | Description |
|---|---|
| base | wds-tabs variant="command". |
Accessibility#
Screen reader#
role="tablist" with aria-orientation. Each wds-tab has role="tab", aria-selected and aria-controls pointing to its panel.
wds-tab-panel has role="tabpanel" named by its tab (aria-labelledby). Panels of other tabs are hidden, also from screen readers.
tabindex="0"), so Tab from the tab list reaches it even when it has no focusable content.
aria-disabled="true". A tab with only an icon needs an accessible name, such as aria-label.
Keyboard support#
| Key | Function |
|---|---|
activation="manual" only moves focus). Disabled tabs are skipped. | |
orientation="vertical". | |
activation="manual"). |