{"slug":"tabs","title":"Tabs","description":"Using the tabs machine in your project.","contentType":"component","framework":"svelte","content":"Tabs organize related content into selectable panels.\n\n## Resources\n\n\n[Latest version: v1.39.1](https://www.npmjs.com/package/@zag-js/tabs)\n[Logic Visualizer](https://zag-visualizer.vercel.app/tabs)\n[Source Code](https://github.com/chakra-ui/zag/tree/main/packages/machines/tabs)\n\n\n\n**Features**\n\n- Supports mouse, touch, and keyboard interactions on tabs\n- Supports LTR and RTL keyboard navigation\n- Follows the tabs ARIA pattern, semantically linking tabs and their associated\n  tab panels\n- Focus management for tab panels without any focusable children\n\n## Installation\n\nInstall the tabs package:\n\n```bash\nnpm install @zag-js/tabs @zag-js/svelte\n# or\nyarn add @zag-js/tabs @zag-js/svelte\n```\n\n## Anatomy\n\nCheck the tabs anatomy and part names.\n\n> Each part includes a `data-part` attribute to help identify them in the DOM.\n\n\n\n## Usage\n\nImport the tabs package:\n\n```tsx\nimport * as tabs from \"@zag-js/tabs\"\n```\n\nThe tabs package exports two key functions:\n\n- `machine` - State machine logic.\n- `connect` - Maps machine state to JSX props and event handlers.\n\nThen use the framework integration helpers:\n\n```svelte\n<script lang=\"ts\">\n  import * as tabs from \"@zag-js/tabs\"\n  import { useMachine, normalizeProps } from \"@zag-js/svelte\"\n\n  const data = [\n    { value: \"item-1\", label: \"Item one\", content: \"Item one content\" },\n    { value: \"item-2\", label: \"Item two\", content: \"Item two content\" },\n    { value: \"item-3\", label: \"Item three\", content: \"Item three content\" },\n  ]\n\n  const id = $props.id()\n  const service = useMachine(tabs.machine, {\n    id,\n    defaultValue: \"item-1\",\n  })\n\n  const api = $derived(tabs.connect(service, normalizeProps))\n</script>\n\n<div {...api.getRootProps()}>\n  <div {...api.getListProps()}>\n    {#each data as item}\n      <button {...api.getTriggerProps({ value: item.value })}>\n        {item.label}\n      </button>\n    {/each}\n  </div>\n  {#each data as item}\n    <div {...api.getContentProps({ value: item.value })}>\n      <p>{item.content}</p>\n    </div>\n  {/each}\n</div>\n```\n\n### Setting the initially selected tab\n\nTo set the initially selected tab, pass the `defaultValue` property to the\nmachine's context.\n\n```tsx {2}\nconst service = useMachine(tabs.machine, {\n  defaultValue: \"tab-1\",\n})\n```\n\nSubsequently, you can use the `api.setValue` function to programmatically set\nthe selected tab.\n\n```tsx\napi.setValue(\"tab-2\")\n```\n\nTo clear selection (when `deselectable` is enabled), use:\n\n```tsx\napi.clearValue()\n```\n\n### Controlled tabs\n\nTo control the selected tab programmatically, pass the `value` and\n`onValueChange` properties to the machine function.\n\n```svelte\n<script lang=\"ts\">\n  let value = $state(\"tab-1\")\n\n  const service = useMachine(tabs.machine, {\n    get value() {\n      return value\n    },\n    onValueChange(details) {\n      value = details.value\n    },\n  })\n</script>\n```\n\n### Changing the orientation\n\nThe default orientation of the tabs is horizontal. To change the orientation,\nset the `orientation` property in the machine's context to `\"vertical\"`.\n\n```tsx {2}\nconst service = useMachine(tabs.machine, {\n  orientation: \"vertical\",\n})\n```\n\n### Showing an indicator\n\nTo show an active indicator when a tab is selected, you add the\n`tabIndicatorProps` object provided by the `connect` function.\n\n```svelte {10}\n\n<!-- ... -->\n<div {...api.getRootProps()}>\n  <div {...api.getListProps()}>\n    {#each data as item}\n      <button {...api.getTriggerProps({ value: item.value })}>\n        {item.label}\n      </button>\n    {/each}\n    <div {...api.getIndicatorProps()}></div>\n  </div>\n  {#each data as item}\n    <div {...api.getContentProps({ value: item.value })}>\n      <p>{item.content}</p>\n    </div>\n  {/each}\n</div>\n<!-- ... -->\n```\n\n### Disabling a tab\n\nTo disable a tab, set the `disabled` property in the `getTriggerProps` to\n`true`.\n\nWhen a Tab is `disabled`, it is skipped during keyboard navigation and it is not\nclickable.\n\n```tsx\n//...\n<button {...api.getTriggerProps({ disabled: true })}></button>\n//...\n```\n\n### Listening for events\n\n- `onValueChange` — Callback invoked when the selected tab changes.\n- `onFocusChange` — Callback invoked when the focused tab changes.\n\n```tsx {2-7}\nconst service = useMachine(tabs.machine, {\n  onFocusChange(details) {\n    // details => { focusedValue: string }\n    console.log(\"focused tab:\", details.focusedValue)\n  },\n  onValueChange(details) {\n    // details => { value: string }\n    console.log(\"selected tab:\", details.value)\n  },\n})\n```\n\n### Allowing deselection\n\nSet `deselectable` to allow clicking the active tab to clear the current value.\n\n```tsx\nconst service = useMachine(tabs.machine, {\n  deselectable: true,\n})\n```\n\n### Custom navigation for link tabs\n\nUse `navigate` when your tab triggers are rendered as links.\n\n```tsx\nconst service = useMachine(tabs.machine, {\n  navigate(details) {\n    // details => { value: string, node: HTMLAnchorElement, href: string }\n    router.push(details.href)\n  },\n})\n```\n\n### Manual tab activation\n\nBy default, a tab is selected when it receives focus from either keyboard or\npointer interaction. This is called \"automatic tab activation\".\n\nThe other approach is \"manual tab activation\" which means the tab is selected\nwith the Enter key or by clicking on the tab.\n\n```tsx {2}\nconst service = useMachine(tabs.machine, {\n  activationMode: \"manual\",\n})\n```\n\n### Looping keyboard navigation\n\nSet `loopFocus` to control whether arrow-key navigation wraps at the ends.\n\n```tsx\nconst service = useMachine(tabs.machine, {\n  loopFocus: false,\n})\n```\n\n### RTL Support\n\nThe tabs machine provides support right to left writing directions. In this\nmode, the layout and keyboard interaction is flipped.\n\nTo enable RTL support, set the `dir` property in the machine's context to `rtl`.\n\n```tsx {2}\nconst service = useMachine(tabs.machine, {\n  dir: \"rtl\",\n})\n```\n\n### Programmatic tab control\n\nUse the connected API for imperative navigation and focus.\n\n```tsx\napi.setValue(\"tab-2\")\napi.selectNext()\napi.selectPrev()\napi.focus()\n```\n\n## Styling guide\n\n### Selected state\n\nWhen a tab is selected, a `data-selected` attribute is added to the trigger and\ncontent elements.\n\n```css\n[data-part=\"trigger\"][data-state=\"active\"] {\n  /* Styles for selected tab */\n}\n\n[data-part=\"content\"][data-state=\"active\"] {\n  /* Styles for selected tab */\n}\n```\n\n### Disabled state\n\nWhen a tab is disabled, a `data-disabled` attribute is added to the trigger\nelement.\n\n```css\n[data-part=\"trigger\"][data-disabled] {\n  /* Styles for disabled tab */\n}\n```\n\n### Focused state\n\nWhen a tab is focused, you the `:focus` or `:focus-visible` pseudo-class to\nstyle it.\n\n```css\n[data-part=\"trigger\"]:focus {\n  /* Styles for focused tab */\n}\n```\n\nWhen any tab is focused, the list is given a `data-focus` attribute.\n\n```css\n[data-part=\"list\"][data-focus] {\n  /* Styles for when any tab is focused */\n}\n```\n\n### Orientation styles\n\nAll parts of the tabs component have the `data-orientation` attribute. You can\nuse this to set the style for the horizontal or vertical tabs.\n\n```css\n[data-part=\"trigger\"][data-orientation=\"(horizontal|vertical)\"] {\n  /* Styles for horizontal/vertical tabs */\n}\n\n[data-part=\"root\"][data-orientation=\"(horizontal|vertical)\"] {\n  /* Styles for horizontal/vertical root */\n}\n\n[data-part=\"indicator\"][data-orientation=\"(horizontal|vertical)\"] {\n  /* Styles for horizontal/vertical tab-indicator */\n}\n\n[data-part=\"list\"][data-orientation=\"(horizontal|vertical)\"] {\n  /* Styles for horizontal/vertical list */\n}\n```\n\n### The tab indicator\n\nThe tab indicator styles have CSS variables for the `transitionDuration` and\n`transitionTimingFunction` defined in it.\n\nThe transition definition is applied when the selected tab changes to allow the\nindicator move smoothly to the new selected tab.\n\n```css\n[data-part=\"indicator\"] {\n  --transition-duration: 0.2s;\n  --transition-timing-function: ease-in-out;\n}\n```\n\n## Methods and Properties\n\n### Machine Context\n\nThe tabs machine exposes the following context properties:\n\n**`ids`**\nType: `Partial<{ root: string; trigger: (value: string) => string; list: string; content: (value: string) => string; indicator: string; }>`\nDescription: The ids of the elements in the tabs. Useful for composition.\n\n**`translations`**\nType: `IntlTranslations`\nDescription: Specifies the localized strings that identifies the accessibility elements and their states\n\n**`loopFocus`**\nType: `boolean`\nDescription: Whether the keyboard navigation will loop from last tab to first, and vice versa.\n\n**`value`**\nType: `string`\nDescription: The controlled selected tab value\n\n**`defaultValue`**\nType: `string`\nDescription: The initial selected tab value when rendered.\nUse when you don't need to control the selected tab value.\n\n**`orientation`**\nType: `\"horizontal\" | \"vertical\"`\nDescription: The orientation of the tabs. Can be `horizontal` or `vertical`\n- `horizontal`: only left and right arrow key navigation will work.\n- `vertical`: only up and down arrow key navigation will work.\n\n**`activationMode`**\nType: `\"manual\" | \"automatic\"`\nDescription: The activation mode of the tabs. Can be `manual` or `automatic`\n- `manual`: Tabs are activated when clicked or press `enter` key.\n- `automatic`: Tabs are activated when receiving focus\n\n**`onValueChange`**\nType: `(details: ValueChangeDetails) => void`\nDescription: Callback to be called when the selected/active tab changes\n\n**`onFocusChange`**\nType: `(details: FocusChangeDetails) => void`\nDescription: Callback to be called when the focused tab changes\n\n**`composite`**\nType: `boolean`\nDescription: Whether the tab is composite\n\n**`deselectable`**\nType: `boolean`\nDescription: Whether the active tab can be deselected when clicking on it.\n\n**`navigate`**\nType: `(details: NavigateDetails) => void`\nDescription: Function to navigate to the selected tab when clicking on it.\nUseful if tab triggers are anchor elements.\n\n**`dir`**\nType: `\"ltr\" | \"rtl\"`\nDescription: The document's text/writing direction.\n\n**`id`**\nType: `string`\nDescription: The unique identifier of the machine.\n\n**`getRootNode`**\nType: `() => ShadowRoot | Node | Document`\nDescription: A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.\n\n### Machine API\n\nThe tabs `api` exposes the following methods:\n\n**`value`**\nType: `string`\nDescription: The current value of the tabs.\n\n**`focusedValue`**\nType: `string`\nDescription: The value of the tab that is currently focused.\n\n**`setValue`**\nType: `(value: string) => void`\nDescription: Sets the value of the tabs.\n\n**`clearValue`**\nType: `VoidFunction`\nDescription: Clears the value of the tabs.\n\n**`setIndicatorRect`**\nType: `(value: string) => void`\nDescription: Sets the indicator rect to the tab with the given value\n\n**`syncTabIndex`**\nType: `VoidFunction`\nDescription: Synchronizes the tab index of the content element.\nUseful when rendering tabs within a select or combobox\n\n**`focus`**\nType: `VoidFunction`\nDescription: Set focus on the selected tab trigger\n\n**`selectNext`**\nType: `(fromValue?: string) => void`\nDescription: Selects the next tab\n\n**`selectPrev`**\nType: `(fromValue?: string) => void`\nDescription: Selects the previous tab\n\n**`getTriggerState`**\nType: `(props: TriggerProps) => TriggerState`\nDescription: Returns the state of the trigger with the given props\n\n### Data Attributes\n\n**`Root`**\n\n**`data-scope`**: tabs\n**`data-part`**: root\n**`data-orientation`**: The orientation of the tabs\n**`data-focus`**: Present when focused\n\n**`List`**\n\n**`data-scope`**: tabs\n**`data-part`**: list\n**`data-focus`**: Present when focused\n**`data-orientation`**: The orientation of the list\n\n**`Trigger`**\n\n**`data-scope`**: tabs\n**`data-part`**: trigger\n**`data-orientation`**: The orientation of the trigger\n**`data-disabled`**: Present when disabled\n**`data-value`**: The value of the item\n**`data-selected`**: Present when selected\n**`data-focus`**: Present when focused\n**`data-ssr`**: Present when not rendered in the browser. Useful for ssr styling\n\n**`Content`**\n\n**`data-scope`**: tabs\n**`data-part`**: content\n**`data-selected`**: Present when selected\n**`data-orientation`**: The orientation of the content\n\n**`Indicator`**\n\n**`data-scope`**: tabs\n**`data-part`**: indicator\n**`data-orientation`**: The orientation of the indicator\n\n### CSS Variables\n\n<CssVarTable name=\"tabs\" />\n\n## Accessibility\n\n### Keyboard Interactions\n\n**`Tab`**\nDescription: When focus moves onto the tabs, focuses the active trigger. When a trigger is focused, moves focus to the active content.\n\n**`ArrowDown`**\nDescription: Moves focus to the next trigger in vertical orientation and activates its associated content.\n\n**`ArrowRight`**\nDescription: Moves focus to the next trigger in horizontal orientation and activates its associated content.\n\n**`ArrowUp`**\nDescription: Moves focus to the previous trigger in vertical orientation and activates its associated content.\n\n**`ArrowLeft`**\nDescription: Moves focus to the previous trigger in horizontal orientation and activates its associated content.\n\n**`Home`**\nDescription: Moves focus to the first trigger and activates its associated content.\n\n**`End`**\nDescription: Moves focus to the last trigger and activates its associated content.\n\n**`Enter + Space`**\nDescription: In manual mode, when a trigger is focused, moves focus to its associated content.","package":"@zag-js/tabs","editUrl":"https://github.com/chakra-ui/zag/edit/main/website/data/components/tabs.mdx"}