Skip to main content

Tabs

The TabGroup component and its children (TabList, Tab, TabPanel) create a fully accessible, unstyled tabbed interface. Inspired by Headless UI’s Tabs, but built natively for Angular.

It gives you complete control over markup and styling while providing accessible keyboard navigation and ARIA roles.

When to use it

Use TabGroup when you need a tabbed UI that is:

  • Fully customizable in layout and appearance
  • Accessible with screen readers and keyboard navigation
  • Controlled via Angular templateRefs or DI
  • Compatible with reactive or template-driven forms

Common use cases:

  • Sectioned content areas
  • Multi-step forms
  • Data toggles

Anatomy

A complete TabGroup is composed of:

  • TabGroup – The root container that manages the tab state
  • TabList – The container for tabs
  • Tab – Each clickable tab option
  • TabPanel – The content area for each tab

Features

  • ✅ Accessible by default (ARIA attributes)
  • ✅ Tailwind-friendly (no styles imposed)
  • ✅ Multiple selectors supported:
    • <TabGroup>
    • <div ngxTabGroup>
    • <ngx-headlessui-tab-group>
  • ✅ Works with both #templateRefs and Angular DI (TabsContextService)
  • ✅ Supports multiple instances per page

Installation

TabGroup ships as part of the @ngx-headless/ui by default. Install if you haven't already.

npm install @ngx-headless/ui

Import the components directly:

import {
TabGroupComponent,
TabListComponent,
TabComponent,
TabPanelComponent,
} from "@ngx-headless/ui";

Usage Examples

Template Reference — Basic Usage

<TabGroup #tabs="ngxTabGroup" [defaultIndex]="0">
<TabList>
<Tab [index]="0">Tab 1</Tab>
<Tab [index]="1">Tab 2</Tab>
</TabList>
<TabPanel [index]="0">Content 1</TabPanel>
<TabPanel [index]="1">Content 2</TabPanel>
</TabGroup>

Template Reference — Access from Parent Component

@ViewChild('tabs') tabsGroup!: TabGroupComponent;

ngAfterViewInit() {
this.tabsGroup.selectTab(0); // 0 or any other index
}

Accessibility

This component handles:

  • role="tablist" on the tab list
  • role="tab" and aria-selected on each tab
  • role="tabpanel" and hidden on each panel
  • Keyboard navigation: , , Home, End to move focus

Animations

TabPanel can be animated freely using Angular’s built-in animation system.

Angular supports entry/exit transitions using @trigger bindings and the :enter/:leave lifecycle hooks.

👉 See Angular Animations Guide

Example

import { trigger, transition, style, animate } from "@angular/animations";

@Component({
animations: [
trigger("fadeInOut", [
transition(":enter", [
style({ opacity: 0 }),
animate("200ms ease-out", style({ opacity: 1 })),
]),
transition(":leave", [
animate("150ms ease-in", style({ opacity: 0 })),
]),
]),
],
})
export class ExampleComponent {}
<TabPanel [index]="0" *ngIf="tabsGroup.getSelectedIndex() === 0" @fadeInOut>
Panel content goes here.
</TabPanel>

✅ You now have full control over entrance and exit behavior of the panel.

Component API

TabGroupComponent

InputTypeDescription
selectedIndex?numberControls the selected tab (controlled)
defaultIndex?numberInitial selected tab (uncontrolled)
classstringClasses applied to the wrapper
OutputTypeDescription
selectedIndexChangeEventEmitter<number>Emits when selected index changes
MethodReturnsDescription
selectTab(index)voidSets the selected tab by index
getSelectedIndex()`numbernull`

TabListComponent

HostBindingTypeDescription
role'tablist'ARIA role automatically assigned

TabComponent

InputTypeDescription
indexnumberThe index this tab represents
disabled?booleanWhether the tab is disabled
HostBindingTypeDescription
role'tab'ARIA role automatically assigned
aria-selectedbooleanWhether this tab is currently selected
tabindexnumberControls keyboard focus

TabPanelComponent

InputTypeDescription
indexnumberThe index this panel represents
HostBindingTypeDescription
role'tabpanel'ARIA role automatically assigned
hiddenbooleanHides the panel when not active

Best Practices

  • Keep tab labels concise and descriptive.
  • Use consistent styling to visually group tabs.
  • Add transitions for panels to create a smooth user experience.
  • Use selectedIndex and selectedIndexChange for two-way binding when needed.

See Also