Overview
The Tab Bar component provides a tabbed navigation interface that allows users to switch between different content panels. Tabs help organize related content into separate views while keeping everything accessible from a single location.
Basic usage
Import the Tab Bar component and define your tabs:
import { TabBarComponent } from '@flowx/angular-ui-toolkit';
@Component({
selector: 'app-example',
standalone: true,
imports: [TabBarComponent],
template: `
<flx-tab-bar
[activeTab]="activeTabIndex"
(tabChange)="onTabChange($event)">
<flx-tab [label]="'Overview'">
<p>Overview content</p>
</flx-tab>
<flx-tab [label]="'Details'">
<p>Details content</p>
</flx-tab>
<flx-tab [label]="'Settings'">
<p>Settings content</p>
</flx-tab>
</flx-tab-bar>
`
})
export class ExampleComponent {
activeTabIndex = 0;
onTabChange(index: number) {
this.activeTabIndex = index;
console.log('Active tab:', index);
}
}
Component selectors
<flx-tab-bar></flx-tab-bar>
<flx-tab></flx-tab>
Tab Bar properties
Index of the currently active tab. Use two-way binding with [(activeTab)] for automatic state synchronization.
variant
'default' | 'pills' | 'underline'
default:"'default'"
Visual style of the tabs:
default: Standard tab appearance
pills: Rounded pill-shaped tabs
underline: Minimal tabs with bottom border indicator
alignment
'start' | 'center' | 'end' | 'stretch'
default:"'start'"
Horizontal alignment of tabs:
start: Tabs aligned to the left
center: Tabs centered
end: Tabs aligned to the right
stretch: Tabs fill available width equally
When true, enables horizontal scrolling for tabs that don’t fit in the available width.
When true, tab content is only rendered when the tab becomes active for the first time.
Tab Bar events
Emitted when the active tab changes. Returns the index of the newly activated tab.
Emitted when the active tab index changes. Useful for two-way binding.
Tab properties
The text label displayed on the tab button.
Icon name to display alongside or instead of the label.
When true, the tab cannot be selected and appears disabled.
Badge content to display on the tab (e.g., notification count).
Unique identifier for the tab. If not provided, an auto-generated ID is used.
Content projection
Each <flx-tab> component uses content projection to display its panel content:
<flx-tab-bar>
<flx-tab [label]="'Tab 1'">
<!-- Content for Tab 1 -->
<div class="tab-content">
<h3>Tab 1 Content</h3>
<p>This content is displayed when Tab 1 is active</p>
</div>
</flx-tab>
<flx-tab [label]="'Tab 2'">
<!-- Content for Tab 2 -->
<div class="tab-content">
<h3>Tab 2 Content</h3>
<p>This content is displayed when Tab 2 is active</p>
</div>
</flx-tab>
</flx-tab-bar>
Examples
Simple tabbed interface with text content:@Component({
template: `
<flx-tab-bar [(activeTab)]="selectedTab">
<flx-tab [label]="'Profile'">
<div class="profile-tab">
<h3>User Profile</h3>
<p>Name: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
</div>
</flx-tab>
<flx-tab [label]="'Preferences'">
<div class="preferences-tab">
<h3>User Preferences</h3>
<label>
<input type="checkbox" [(ngModel)]="preferences.notifications" />
Enable notifications
</label>
</div>
</flx-tab>
<flx-tab [label]="'Security'">
<div class="security-tab">
<h3>Security Settings</h3>
<button (click)="changePassword()">Change Password</button>
</div>
</flx-tab>
</flx-tab-bar>
`
})
export class UserSettingsComponent {
selectedTab = 0;
user = { name: 'John Doe', email: 'john@example.com' };
preferences = { notifications: true };
changePassword() {
console.log('Change password');
}
}
Enhance tabs with icons and notification badges:@Component({
template: `
<flx-tab-bar
[variant]="'pills'"
[alignment]="'center'">
<flx-tab
[label]="'Messages'"
[icon]="'mail'"
[badge]="unreadMessages">
<div class="messages-content">
<div *ngFor="let message of messages" class="message">
{{ message.text }}
</div>
</div>
</flx-tab>
<flx-tab
[label]="'Notifications'"
[icon]="'bell'"
[badge]="notifications.length">
<div class="notifications-content">
<div *ngFor="let notif of notifications" class="notification">
{{ notif.text }}
</div>
</div>
</flx-tab>
<flx-tab
[label]="'Activity'"
[icon]="'activity'">
<div class="activity-content">
<p>Recent activity log</p>
</div>
</flx-tab>
</flx-tab-bar>
`
})
export class NotificationCenterComponent {
unreadMessages = 5;
messages = [
{ text: 'New message from Alice' },
{ text: 'New message from Bob' }
];
notifications = [
{ text: 'System update available' },
{ text: 'New comment on your post' },
{ text: 'Weekly report ready' }
];
}
Generate tabs dynamically from data:@Component({
template: `
<div class="tab-controls">
<button (click)="addTab()">Add Tab</button>
<button (click)="removeTab()" [disabled]="tabs.length <= 1">
Remove Last Tab
</button>
</div>
<flx-tab-bar [(activeTab)]="activeIndex">
<flx-tab
*ngFor="let tab of tabs; let i = index"
[label]="tab.label"
[disabled]="tab.disabled">
<div class="dynamic-content">
<h3>{{ tab.title }}</h3>
<p>{{ tab.content }}</p>
</div>
</flx-tab>
</flx-tab-bar>
`
})
export class DynamicTabsComponent {
activeIndex = 0;
tabCounter = 3;
tabs = [
{
label: 'Tab 1',
title: 'First Tab',
content: 'Content for the first tab',
disabled: false
},
{
label: 'Tab 2',
title: 'Second Tab',
content: 'Content for the second tab',
disabled: false
}
];
addTab() {
this.tabCounter++;
this.tabs.push({
label: `Tab ${this.tabCounter}`,
title: `Tab Number ${this.tabCounter}`,
content: `Dynamic content for tab ${this.tabCounter}`,
disabled: false
});
}
removeTab() {
if (this.tabs.length > 1) {
this.tabs.pop();
if (this.activeIndex >= this.tabs.length) {
this.activeIndex = this.tabs.length - 1;
}
}
}
}
Load tab content only when the tab is activated:@Component({
template: `
<flx-tab-bar
[lazy]="true"
(tabChange)="onTabActivated($event)">
<flx-tab [label]="'Dashboard'">
<app-dashboard *ngIf="loadedTabs.includes(0)"></app-dashboard>
</flx-tab>
<flx-tab [label]="'Analytics'">
<div *ngIf="loadedTabs.includes(1)">
<app-analytics [data]="analyticsData"></app-analytics>
</div>
</flx-tab>
<flx-tab [label]="'Reports'">
<div *ngIf="loadedTabs.includes(2)">
<app-reports [reports]="reports"></app-reports>
</div>
</flx-tab>
</flx-tab-bar>
`
})
export class LazyTabsComponent {
loadedTabs: number[] = [0]; // Load first tab by default
analyticsData: any = null;
reports: any[] = [];
async onTabActivated(index: number) {
if (!this.loadedTabs.includes(index)) {
this.loadedTabs.push(index);
// Load data for the activated tab
if (index === 1) {
this.analyticsData = await this.loadAnalyticsData();
} else if (index === 2) {
this.reports = await this.loadReports();
}
}
}
async loadAnalyticsData() {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return { views: 1234, clicks: 567 };
}
async loadReports() {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
return [
{ id: 1, name: 'Monthly Report' },
{ id: 2, name: 'Quarterly Report' }
];
}
}
Enable horizontal scrolling for many tabs:@Component({
template: `
<flx-tab-bar
[scrollable]="true"
[variant]="'underline'">
<flx-tab
*ngFor="let category of categories"
[label]="category.name"
[icon]="category.icon">
<div class="category-content">
<h3>{{ category.name }}</h3>
<div class="products">
<div *ngFor="let product of category.products" class="product">
{{ product.name }}
</div>
</div>
</div>
</flx-tab>
</flx-tab-bar>
`,
styles: [`
.category-content {
padding: 1.5rem;
}
.products {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.product {
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
`]
})
export class ProductCatalogComponent {
categories = [
{
name: 'Electronics',
icon: 'devices',
products: [{ name: 'Laptop' }, { name: 'Phone' }]
},
{
name: 'Clothing',
icon: 'shirt',
products: [{ name: 'T-Shirt' }, { name: 'Jeans' }]
},
{
name: 'Home & Garden',
icon: 'home',
products: [{ name: 'Chair' }, { name: 'Table' }]
},
{
name: 'Sports',
icon: 'sports',
products: [{ name: 'Ball' }, { name: 'Racket' }]
},
{
name: 'Books',
icon: 'book',
products: [{ name: 'Novel' }, { name: 'Magazine' }]
}
// Add more categories as needed
];
}
Organize complex forms using tabs:@Component({
template: `
<form [formGroup]="registrationForm">
<flx-tab-bar [(activeTab)]="currentStep">
<flx-tab [label]="'Personal Info'">
<div class="form-section">
<h3>Personal Information</h3>
<input
type="text"
formControlName="firstName"
placeholder="First Name" />
<input
type="text"
formControlName="lastName"
placeholder="Last Name" />
<input
type="email"
formControlName="email"
placeholder="Email" />
</div>
</flx-tab>
<flx-tab
[label]="'Address'"
[disabled]="!registrationForm.get('firstName')?.valid">
<div class="form-section">
<h3>Address</h3>
<input
type="text"
formControlName="street"
placeholder="Street" />
<input
type="text"
formControlName="city"
placeholder="City" />
<input
type="text"
formControlName="zipCode"
placeholder="ZIP Code" />
</div>
</flx-tab>
<flx-tab
[label]="'Review'"
[disabled]="!registrationForm.valid">
<div class="form-section">
<h3>Review Your Information</h3>
<dl>
<dt>Name:</dt>
<dd>{{ registrationForm.value.firstName }} {{ registrationForm.value.lastName }}</dd>
<dt>Email:</dt>
<dd>{{ registrationForm.value.email }}</dd>
<dt>Address:</dt>
<dd>{{ registrationForm.value.street }}, {{ registrationForm.value.city }}</dd>
</dl>
<button
type="submit"
[disabled]="!registrationForm.valid"
(click)="submitForm()">
Submit Registration
</button>
</div>
</flx-tab>
</flx-tab-bar>
</form>
`
})
export class RegistrationFormComponent {
currentStep = 0;
registrationForm: FormGroup;
constructor(private fb: FormBuilder) {
this.registrationForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
street: ['', Validators.required],
city: ['', Validators.required],
zipCode: ['', Validators.required]
});
}
submitForm() {
if (this.registrationForm.valid) {
console.log('Form submitted:', this.registrationForm.value);
}
}
}
When using the lazy property, ensure that tab content components can handle being destroyed and recreated if users switch between tabs multiple times.
Accessibility
The Tab Bar component follows accessibility best practices:
- Uses proper ARIA attributes (
role="tablist", role="tab", role="tabpanel")
- Supports keyboard navigation:
- Arrow keys to move between tabs
- Home/End keys to jump to first/last tab
- Tab key to enter tab panel content
- Announces active tab and panel content to screen readers
- Manages focus appropriately when switching tabs
Avoid using too many tabs (more than 7-8) as this can overwhelm users. Consider using a dropdown menu or different navigation pattern for large numbers of options.
Styling
Customize tab bar appearance using CSS custom properties:
flx-tab-bar {
--tab-bar-background: transparent;
--tab-bar-border-color: #e0e0e0;
--tab-color: #666666;
--tab-active-color: #1976d2;
--tab-hover-background: #f5f5f5;
--tab-active-background: #e3f2fd;
--tab-indicator-color: #1976d2;
--tab-indicator-height: 2px;
--tab-padding: 12px 24px;
--tab-border-radius: 4px;
}
Common use cases
- Settings panels: Organize settings into logical categories
- Product details: Show different aspects of a product (overview, specs, reviews)
- Dashboard views: Switch between different data visualizations
- Multi-step forms: Guide users through form completion
- Documentation: Organize code examples or different documentation sections
- Content categories: Browse content organized by category
- User profiles: Display different sections of user information