Skip to main content
0.48.0
View Zag.js on Github
Join the Discord server

Tabs

An accessible tabs component that provides keyboard interactions and ARIA attributes described in the WAI-ARIA Tabs Design Pattern. Tabs consist of a tab list with one or more visually separated tabs. Each tab has associated content, and only the selected tab's content is shown.

Item one content

Properties

Features

  • Support for mouse, touch, and keyboard interactions on tabs.
  • Support for LTR and RTL keyboard navigation.
  • Support for disabled tabs.
  • Follows the tabs ARIA pattern, semantically linking tabs and their associated tab panels.
  • Focus management for tab panels without any focusable children

Installation

To use the tabs machine in your project, run the following command in your command line:

npm install @zag-js/tabs @zag-js/react # or yarn add @zag-js/tabs @zag-js/react

This command will install the framework agnostic tabs logic and the reactive utilities for your framework of choice.

Anatomy

To set up the tabs correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

Usage

First, import the tabs package into your project

import * as tabs from "@zag-js/tabs"

The tabs package exports two key functions:

  • machine — The state machine logic for the tabs widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the tabs machine in your project 🔥

import * as tabs from "@zag-js/tabs" import { useMachine, normalizeProps } from "@zag-js/react" const data = [ { value: "item-1", label: "Item one", content: "Item one content" }, { value: "item-2", label: "Item two", content: "Item two content" }, { value: "item-3", label: "Item three", content: "Item three content" }, ] export function Tabs() { const [state, send] = useMachine(tabs.machine({ id: "1", value: "item-1" })) const api = tabs.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <div {...api.listProps}> {data.map((item) => ( <button {...api.getTriggerProps({ value: item.value })} key={item.value} > {item.label} </button> ))} </div> {data.map((item) => ( <div {...api.getContentProps({ value: item.value })} key={item.value}> <p>{item.content}</p> </div> ))} </div> ) }

Setting the selected tab

To set the initially selected tab, pass the value property to the machine's context.

const [state, send] = useMachine( tabs.machine({ value: "tab-1", }), )

Subsequently, you can use the api.setValue function to set the selected tab.

Changing the orientation

The default orientation of the tabs is horizontal. To change the orientation, set the orientation property in the machine's context to "vertical".

const [state, send] = useMachine( tabs.machine({ orientation: "vertical", }), )

Showing an indicator

To show an active indicator when a tab is selected, you add the tabIndicatorProps object provided by the connect function.

// ... return ( <div {...api.rootProps}> <div {...api.listProps}> {data.map((item) => ( <button {...api.getTriggerProps({ value: item.value })} key={item.value} > {item.label} </button> ))} <div {...api.indicatorProps} /> </div> {data.map((item) => ( <div {...api.getContentProps({ value: item.value })} key={item.value}> <p>{item.content}</p> </div> ))} </div> )

Disabling a tab

To disable a tab, set the disabled property in the getTriggerProps to true.

When a Tab is disabled, it is skipped during keyboard navigation and it is not clickable.

//... <button {...api.getTriggerProps({ disabled: true })}></button> //...

Listening for events

  • onValueChange — Callback invoked when the selected tab changes.
  • onFocusChange — Callback invoked when the focused tab changes.
const [state, send] = useMachine( tabs.machine({ onFocusChange(details) { // details => { value: string | null } console.log("focused tab:", details.value) }, onValueChange(details) { // details => { value: string } console.log("selected tab:", details.value) }, }), )

Manual tab activation

By default, the tab can be selected when the receive focus from either the keyboard or pointer interaction. This is called "automatic tab activation".

The other approach is "manual tab activation" which means the tab is selected with the Enter key or by clicking on the tab.

const [state, send] = useMachine( tabs.machine({ activationMode: "manual", }), )

RTL Support

The tabs machine provides support right to left writing directions. In this mode, the layout and keyboard interaction is flipped.

To enable RTL support, set the dir property in the machine's context to rtl.

const [state, send] = useMachine( tabs.machine({ dir: "rtl", }), )

Styling guide

Selected state

When a tab is selected, a data-selected attribute is added to the trigger and content elements.

[data-part="trigger"][data-state="active"] { /* Styles for selected tab */ } [data-part="content"][data-state="active"] { /* Styles for selected tab */ }

Disabled state

When a tab is disabled, a data-disabled attribute is added to the trigger element.

[data-part="trigger"][data-disabled] { /* Styles for disabled tab */ }

Focused state

When a tab is focused, you the :focus or :focus-visible pseudo-class to style it.

[data-part="trigger"]:focus { /* Styles for focused tab */ }

When any tab is focused, the list is given a data-focus attribute.

[data-part="list"][data-focus] { /* Styles for when any tab is focused */ }

Orientation styles

All parts of the tabs component have the data-orientation attribute. You can use this to set the style for the horizontal or vertical tabs.

[data-part="trigger"][data-orientation="(horizontal|vertical)"] { /* Styles for horizontal/vertical tabs */ } [data-part="root"][data-orientation="(horizontal|vertical)"] { /* Styles for horizontal/vertical root */ } [data-part="indicator"][data-orientation="(horizontal|vertical)"] { /* Styles for horizontal/vertical tab-indicator */ } [data-part="list"][data-orientation="(horizontal|vertical)"] { /* Styles for horizontal/vertical list */ }

The tab indicator

The tab indicator styles have CSS variables for the transitionDuration and transitionTimingFunction defined in it.

The transition definition is applied when the selected tab changes to allow the indicator move smoothly to the new selected tab.

[data-part="indicator"] { --transition-duration: 0.2s; --transition-timing-function: ease-in-out; }

You'll also need to set the styles for the indicator to match your design.

[data-part="indicator"] { --transition-duration: 0.2s; --transition-timing-function: ease-in-out; }

Methods and Properties

Machine Context

The tabs machine exposes the following context properties:

  • idsPartial<{ root: string; trigger: string; list: string; content: string; indicator: string; }>The ids of the elements in the tabs. Useful for composition.
  • translationsIntlTranslationsSpecifies the localized strings that identifies the accessibility elements and their states
  • loopFocusbooleanWhether the keyboard navigation will loop from last tab to first, and vice versa.
  • valuestringThe selected tab id
  • orientation"horizontal" | "vertical"The orientation of the tabs. Can be `horizontal` or `vertical` - `horizontal`: only left and right arrow key navigation will work. - `vertical`: only up and down arrow key navigation will work.
  • activationMode"manual" | "automatic"The activation mode of the tabs. Can be `manual` or `automatic` - `manual`: Tabs are activated when clicked or press `enter` key. - `automatic`: Tabs are activated when receiving focus
  • onValueChange(details: ValueChangeDetails) => voidCallback to be called when the selected/active tab changes
  • onFocusChange(details: FocusChangeDetails) => voidCallback to be called when the focused tab changes
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The tabs api exposes the following methods:

  • valuestringThe current value of the tabs.
  • focusedValuestringThe value of the tab that is currently focused.
  • setValue(value: string) => voidSets the value of the tabs.
  • clearValue() => voidClears the value of the tabs.
  • setIndicatorRect(value: string) => voidSets the indicator rect to the tab with the given value
  • getTriggerState(props: TriggerProps) => TriggerStateReturns the state of the trigger with the given props

Accessibility

Keyboard Interactions

  • Tab
    When focus moves onto the tabs, focuses the active trigger. When a trigger is focused, moves focus to the active content.
  • ArrowDown
    Moves focus to the next trigger in vertical orientation and activates its associated content.
  • ArrowRight
    Moves focus to the next trigger in horizontal orientation and activates its associated content.
  • ArrowUp
    Moves focus to the previous trigger in vertical orientation and activates its associated content.
  • ArrowLeft
    Moves focus to the previous trigger in horizontal orientation and activates its associated content.
  • Home
    Moves focus to the first trigger and activates its associated content.
  • End
    Moves focus to the last trigger and activates its associated content.
  • EnterSpace
    In manual mode, when a trigger is focused, moves focus to its associated content.

Edit this page on GitHub

On this page