@commercetools/nimbus-mcp 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/data/docs/route-manifest.json +1608 -143
- package/data/docs/routes/components-buttons-button.json +25 -2
- package/data/docs/routes/components-chat-chat-message-list.json +448 -0
- package/data/docs/routes/components-chat-chat-message.json +580 -0
- package/data/docs/routes/components-content-markdown.json +548 -0
- package/data/docs/routes/components-data-display-item-group.json +413 -0
- package/data/docs/routes/components-data-display-item.json +611 -0
- package/data/docs/routes/components-feedback-activity-indicator.json +396 -0
- package/data/docs/routes/components-feedback-skeleton.json +481 -0
- package/data/docs/routes/components-feedback-skeletoncircle.json +429 -0
- package/data/docs/routes/components-feedback-skeletontext.json +417 -0
- package/data/docs/routes/components-feedback-tooltip.json +23 -1
- package/data/docs/routes/components-inputs-drop-zone.json +562 -0
- package/data/docs/routes/components-inputs-file-trigger.json +421 -0
- package/data/docs/routes/components-inputs-range-slider.json +633 -0
- package/data/docs/routes/components-inputs-slider.json +611 -0
- package/data/docs/routes/components-layout-splitter.json +3 -3
- package/data/docs/routes/components-media-avatar.json +16 -5
- package/data/docs/routes/components-navigation-breadcrumbs.json +565 -0
- package/data/docs/routes/components-navigation-menu.json +1 -1
- package/data/docs/routes/components-navigation-tabnav.json +80 -14
- package/data/docs/routes/components-navigation-tabs.json +44 -11
- package/data/docs/routes/components-navigation-tree.json +445 -0
- package/data/docs/routes/home-getting-started-mcp-server-overview.json +2 -2
- package/data/docs/routes/home-getting-started-mcp-server-setup.json +2 -2
- package/data/docs/routes/icons.json +3 -3
- package/data/docs/routes/patterns-buttons-floating-action-button.json +392 -0
- package/data/docs/routes/patterns-buttons.json +58 -0
- package/data/docs/search-index.json +1 -1
- package/data/docs/types/AccordionContent.json +24 -24
- package/data/docs/types/AccordionHeader.json +88 -88
- package/data/docs/types/AccordionItem.json +20 -20
- package/data/docs/types/AccordionRoot.json +14 -14
- package/data/docs/types/ActivityIndicator.json +202 -0
- package/data/docs/types/AlertDescription.json +8 -8
- package/data/docs/types/AlertDismissButton.json +98 -81
- package/data/docs/types/AlertTitle.json +8 -8
- package/data/docs/types/Avatar.json +19 -2
- package/data/docs/types/Badge.json +2 -2
- package/data/docs/types/Body.json +6 -6
- package/data/docs/types/Box.json +6 -6
- package/data/docs/types/Breadcrumbs.json +12 -0
- package/data/docs/types/BreadcrumbsItem.json +296 -0
- package/data/docs/types/BreadcrumbsItemProps.json +9 -0
- package/data/docs/types/BreadcrumbsProps.json +9 -0
- package/data/docs/types/BreadcrumbsRoot.json +110 -0
- package/data/docs/types/Button.json +106 -89
- package/data/docs/types/Calendar.json +56 -56
- package/data/docs/types/Caption.json +6 -6
- package/data/docs/types/CardRoot.json +2 -2
- package/data/docs/types/Cell.json +6 -6
- package/data/docs/types/ChatMessage.json +12 -0
- package/data/docs/types/ChatMessageActions.json +42 -0
- package/data/docs/types/ChatMessageAvatar.json +140 -0
- package/data/docs/types/ChatMessageBody.json +42 -0
- package/data/docs/types/ChatMessageList.json +12 -0
- package/data/docs/types/ChatMessageListItem.json +42 -0
- package/data/docs/types/ChatMessageListRoot.json +78 -0
- package/data/docs/types/ChatMessageMeta.json +42 -0
- package/data/docs/types/ChatMessageRoot.json +119 -0
- package/data/docs/types/ChatMessageTyping.json +42 -0
- package/data/docs/types/Checkbox.json +86 -86
- package/data/docs/types/Code.json +11 -11
- package/data/docs/types/Column.json +6 -6
- package/data/docs/types/ColumnGroup.json +6 -6
- package/data/docs/types/ColumnHeader.json +6 -6
- package/data/docs/types/ComboBoxListBox.json +70 -70
- package/data/docs/types/ComboBoxOption.json +64 -64
- package/data/docs/types/ComboBoxPopover.json +91 -70
- package/data/docs/types/ComboBoxRoot.json +8 -8
- package/data/docs/types/ComboBoxSection.json +24 -24
- package/data/docs/types/ComboBoxTrigger.json +6 -6
- package/data/docs/types/DataTable.json +2 -2
- package/data/docs/types/DataTableBody.json +20 -20
- package/data/docs/types/DataTableHeader.json +25 -25
- package/data/docs/types/DataTableRoot.json +2 -2
- package/data/docs/types/DataTableTable.json +6 -6
- package/data/docs/types/DateInput.json +73 -73
- package/data/docs/types/DatePicker.json +58 -58
- package/data/docs/types/DateRangePicker.json +86 -86
- package/data/docs/types/DateRangePickerField.json +86 -86
- package/data/docs/types/DefaultPageRoot.json +2 -2
- package/data/docs/types/DialogCloseTrigger.json +96 -79
- package/data/docs/types/DraggableListField.json +88 -60
- package/data/docs/types/DraggableListItem.json +52 -52
- package/data/docs/types/DraggableListRoot.json +88 -60
- package/data/docs/types/DrawerCloseTrigger.json +96 -79
- package/data/docs/types/DropZone.json +161 -0
- package/data/docs/types/FieldErrors.json +2 -2
- package/data/docs/types/FileTrigger.json +130 -0
- package/data/docs/types/Flex.json +22 -22
- package/data/docs/types/FloatingActionButton.json +1036 -0
- package/data/docs/types/Footer.json +6 -6
- package/data/docs/types/FormActionBar.json +1 -1
- package/data/docs/types/FormFieldRoot.json +2 -2
- package/data/docs/types/Grid.json +24 -24
- package/data/docs/types/Group.json +12 -12
- package/data/docs/types/Header.json +6 -6
- package/data/docs/types/Heading.json +8 -8
- package/data/docs/types/Icon.json +4 -4
- package/data/docs/types/IconButton.json +106 -89
- package/data/docs/types/IconToggleButton.json +71 -71
- package/data/docs/types/Image.json +43 -43
- package/data/docs/types/Indicator.json +6 -6
- package/data/docs/types/InlineSvg.json +2 -2
- package/data/docs/types/Item.json +6 -6
- package/data/docs/types/ItemActions.json +27 -0
- package/data/docs/types/ItemContent.json +27 -0
- package/data/docs/types/ItemDescription.json +27 -0
- package/data/docs/types/ItemFooter.json +27 -0
- package/data/docs/types/ItemGroup.json +12 -0
- package/data/docs/types/ItemGroupRoot.json +27 -0
- package/data/docs/types/ItemGroupSeparator.json +27 -0
- package/data/docs/types/ItemHeader.json +27 -0
- package/data/docs/types/ItemMedia.json +56 -0
- package/data/docs/types/ItemRoot.json +239 -0
- package/data/docs/types/ItemTitle.json +27 -0
- package/data/docs/types/Kbd.json +8 -8
- package/data/docs/types/Link.json +14 -14
- package/data/docs/types/ListIndicator.json +6 -6
- package/data/docs/types/ListItem.json +6 -6
- package/data/docs/types/ListRoot.json +11 -11
- package/data/docs/types/LoadingSpinner.json +2 -2
- package/data/docs/types/MakeElementFocusable.json +18 -18
- package/data/docs/types/Markdown.json +183 -0
- package/data/docs/types/MenuItem.json +62 -62
- package/data/docs/types/MenuRoot.json +55 -55
- package/data/docs/types/MenuSection.json +30 -30
- package/data/docs/types/MenuSubmenuTrigger.json +4 -4
- package/data/docs/types/MenuTrigger.json +88 -88
- package/data/docs/types/MultilineTextInput.json +104 -104
- package/data/docs/types/MultilineTextInputField.json +102 -102
- package/data/docs/types/NumberInput.json +86 -86
- package/data/docs/types/NumberInputField.json +82 -82
- package/data/docs/types/PageContentColumn.json +6 -6
- package/data/docs/types/PageContentRoot.json +6 -6
- package/data/docs/types/PasswordInput.json +100 -100
- package/data/docs/types/PasswordInputField.json +100 -100
- package/data/docs/types/ProgressBar.json +13 -13
- package/data/docs/types/RadioInputOption.json +56 -56
- package/data/docs/types/RadioInputRoot.json +48 -48
- package/data/docs/types/RangeCalendar.json +58 -58
- package/data/docs/types/RangeSlider.json +337 -0
- package/data/docs/types/RangeSliderProps.json +9 -0
- package/data/docs/types/Region.json +6 -6
- package/data/docs/types/RegionTarget.json +6 -6
- package/data/docs/types/RichTextInput.json +2 -2
- package/data/docs/types/Root.json +11 -11
- package/data/docs/types/Row.json +6 -6
- package/data/docs/types/ScrollArea.json +2 -2
- package/data/docs/types/SearchInput.json +106 -106
- package/data/docs/types/SearchInputField.json +102 -102
- package/data/docs/types/SelectOption.json +54 -54
- package/data/docs/types/SelectOptionGroup.json +18 -18
- package/data/docs/types/SelectOptions.json +64 -64
- package/data/docs/types/SelectRoot.json +92 -92
- package/data/docs/types/Separator.json +4 -4
- package/data/docs/types/SimpleGrid.json +28 -28
- package/data/docs/types/Skeleton.json +129 -0
- package/data/docs/types/SkeletonCircle.json +103 -0
- package/data/docs/types/SkeletonCircleProps.json +9 -0
- package/data/docs/types/SkeletonProps.json +9 -0
- package/data/docs/types/SkeletonText.json +62 -0
- package/data/docs/types/SkeletonTextProps.json +9 -0
- package/data/docs/types/Slider.json +337 -0
- package/data/docs/types/SlidingIndicatorGeometry.json +9 -0
- package/data/docs/types/SlidingIndicatorRects.json +9 -0
- package/data/docs/types/SplitButton.json +16 -20
- package/data/docs/types/Stack.json +2 -2
- package/data/docs/types/StepsRoot.json +2 -2
- package/data/docs/types/Switch.json +34 -34
- package/data/docs/types/TabNavItem.json +5 -5
- package/data/docs/types/TabNavRoot.json +5 -5
- package/data/docs/types/TableBody.json +6 -6
- package/data/docs/types/TableCaption.json +6 -6
- package/data/docs/types/TableCell.json +6 -6
- package/data/docs/types/TableColumn.json +6 -6
- package/data/docs/types/TableColumnGroup.json +6 -6
- package/data/docs/types/TableColumnHeader.json +6 -6
- package/data/docs/types/TableFooter.json +6 -6
- package/data/docs/types/TableHeader.json +6 -6
- package/data/docs/types/TableRoot.json +19 -19
- package/data/docs/types/TableRow.json +6 -6
- package/data/docs/types/TableScrollArea.json +6 -6
- package/data/docs/types/TabsList.json +2 -2
- package/data/docs/types/TabsPanel.json +2 -2
- package/data/docs/types/TabsPanels.json +2 -2
- package/data/docs/types/TabsRoot.json +3 -3
- package/data/docs/types/TabsTab.json +2 -2
- package/data/docs/types/TagGroupRoot.json +24 -24
- package/data/docs/types/TagGroupTag.json +56 -56
- package/data/docs/types/TagGroupTagList.json +14 -14
- package/data/docs/types/Text.json +8 -8
- package/data/docs/types/TextInput.json +102 -102
- package/data/docs/types/TextInputField.json +100 -100
- package/data/docs/types/TimeInput.json +68 -68
- package/data/docs/types/ToggleButton.json +73 -73
- package/data/docs/types/ToggleButtonGroupButton.json +30 -30
- package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
- package/data/docs/types/Toolbar.json +9 -9
- package/data/docs/types/TooltipContent.json +28 -28
- package/data/docs/types/TooltipRoot.json +16 -16
- package/data/docs/types/Tree.json +12 -0
- package/data/docs/types/TreeIndicator.json +828 -0
- package/data/docs/types/TreeItem.json +559 -0
- package/data/docs/types/TreeItemContent.json +27 -0
- package/data/docs/types/TreeRoot.json +636 -0
- package/data/docs/types/TreeSubTree.json +40 -0
- package/data/docs/types/VisuallyHidden.json +6 -6
- package/data/docs/types/createItemsFromCsvDrop.json +4 -4
- package/data/docs/types/createItemsFromDirectoryDrop.json +4 -4
- package/data/docs/types/createItemsFromFileDrop.json +4 -4
- package/data/docs/types/createItemsFromImageDrop.json +4 -4
- package/data/docs/types/createItemsFromJsonDrop.json +4 -4
- package/data/docs/types/manifest.json +50 -0
- package/data/docs/types/useRegion.json +2 -2
- package/data/docs/types/useSlidingIndicator.json +145 -0
- package/data/docs/types/useTree.json +267 -0
- package/data/tokens.json +21 -0
- package/data/uikit-tokens.json +894 -0
- package/dist/index.js +3298 -109
- package/package.json +8 -6
|
@@ -94,6 +94,17 @@
|
|
|
94
94
|
2
|
|
95
95
|
],
|
|
96
96
|
"parent": "root"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"value": "Variants",
|
|
100
|
+
"href": "#variants",
|
|
101
|
+
"depth": 3,
|
|
102
|
+
"numbering": [
|
|
103
|
+
1,
|
|
104
|
+
3,
|
|
105
|
+
2
|
|
106
|
+
],
|
|
107
|
+
"parent": "root"
|
|
97
108
|
}
|
|
98
109
|
],
|
|
99
110
|
"figmaLink": "https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=8571-34966&m=dev",
|
|
@@ -121,10 +132,10 @@
|
|
|
121
132
|
}
|
|
122
133
|
]
|
|
123
134
|
},
|
|
124
|
-
"mdx": "\n## Overview\n\nTabs are a navigational component used to organize and switch between different\nsections of content within the same contextual view, allowing users to access\nmultiple related pages or views without navigating away from the main screen.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=8571-34966&m=dev)\n[React Aria Docs](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n\n## Tabs vs TabNav\n\n`Tabs` and `TabNav` look identical but serve fundamentally different purposes.\n\n| | `Tabs` | `TabNav` |\n|---|---|---|\n| **Use case** | Switching content panels within a page | Page-level navigation between routes |\n| **HTML semantics** | `role=\"tablist\"` + `role=\"tab\"` | `<nav>` + `<a>` |\n| **Active state** | `aria-selected=\"true\"` | `aria-current=\"page\"` |\n| **Keyboard** | Arrow keys (roving tabindex) | Sequential Tab key |\n| **Content** | Owns associated `TabPanel` | No panel — content managed by router |\n| **URL** | Optional (`href` prop) | Always (`href` required) |\n\nBoth `Tabs` and `TabNav` support client-side routing — the deciding factor is whether the component owns its content panels or delegates to the router. Use `Tabs` when the content lives alongside the tab list. Use [`TabNav`](/components/navigation/tab-nav) when each item navigates to a different route and the router manages what's rendered.\n\n## Variables\n\nGet familiar with the features.\n\n### Direction for tabs\n\nTabs can be oriented horizontally or vertically to suit different layout\nrequirements.\n\n#### Horizontal tabs\n\nOrganize and display distinct, related sections of content immediately beneath a\ncommon navigation bar or header, serving primarily to group content contextually\nwhile conserving vertical screen space.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Large</Text>\n <Tabs.Root size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Medium</Text>\n <Tabs.Root size=\"md\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Small</Text>\n <Tabs.Root size=\"sm\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n#### Vertical tabs\n\nMaximize horizontal reading space and accommodate a larger number of\nconsistently visible options than traditional horizontal tabs allow.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"1000\">\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical left</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical right</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n",
|
|
135
|
+
"mdx": "\n## Overview\n\nTabs are a navigational component used to organize and switch between different\nsections of content within the same contextual view, allowing users to access\nmultiple related pages or views without navigating away from the main screen.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=8571-34966&m=dev)\n[React Aria Docs](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n\n## Tabs vs TabNav\n\n`Tabs` and `TabNav` look identical but serve fundamentally different purposes.\n\n| | `Tabs` | `TabNav` |\n|---|---|---|\n| **Use case** | Switching content panels within a page | Page-level navigation between routes |\n| **HTML semantics** | `role=\"tablist\"` + `role=\"tab\"` | `<nav>` + `<a>` |\n| **Active state** | `aria-selected=\"true\"` | `aria-current=\"page\"` |\n| **Keyboard** | Arrow keys (roving tabindex) | Sequential Tab key |\n| **Content** | Owns associated `TabPanel` | No panel — content managed by router |\n| **URL** | Optional (`href` prop) | Always (`href` required) |\n\nBoth `Tabs` and `TabNav` support client-side routing — the deciding factor is whether the component owns its content panels or delegates to the router. Use `Tabs` when the content lives alongside the tab list. Use [`TabNav`](/components/navigation/tab-nav) when each item navigates to a different route and the router manages what's rendered.\n\n## Variables\n\nGet familiar with the features.\n\n### Direction for tabs\n\nTabs can be oriented horizontally or vertically to suit different layout\nrequirements.\n\n#### Horizontal tabs\n\nOrganize and display distinct, related sections of content immediately beneath a\ncommon navigation bar or header, serving primarily to group content contextually\nwhile conserving vertical screen space.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Large</Text>\n <Tabs.Root size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Medium</Text>\n <Tabs.Root size=\"md\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Small</Text>\n <Tabs.Root size=\"sm\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n#### Vertical tabs\n\nMaximize horizontal reading space and accommodate a larger number of\nconsistently visible options than traditional horizontal tabs allow.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"1000\">\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical left</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical right</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n### Variants\n\nTabs ships with three visual variants (the same set as `TabNav`): `line`\n(default), `rounded`, and `pill`. The active marker always slides between tabs as\nthe selection changes, rather than snapping — a thin bar on the active tab's edge\nfor `line` (an underline when horizontal, an inner side bar when vertical), or a\nfilled highlight for `rounded`/`pill`. The slide is decorative (it does not\naffect selection, focus, or keyboard behavior) and automatically respects the\nuser's reduced-motion preference (snapping instead of sliding).\n\n```jsx live\nconst App = () => {\n const tabs = [\n { id: 'home', tabLabel: 'Home', panelContent: 'Home content' },\n { id: 'about', tabLabel: 'About', panelContent: 'About content' },\n { id: 'contact', tabLabel: 'Contact', panelContent: 'Contact content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"800\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Line</Text>\n <Tabs.Root variant=\"line\" tabs={tabs} />\n </Stack>\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Rounded</Text>\n <Tabs.Root variant=\"rounded\" tabs={tabs} />\n </Stack>\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Pill</Text>\n <Tabs.Root variant=\"pill\" tabs={tabs} />\n </Stack>\n </Stack>\n )\n}\n```\n",
|
|
125
136
|
"views": {
|
|
126
137
|
"overview": {
|
|
127
|
-
"mdx": "\n## Overview\n\nTabs are a navigational component used to organize and switch between different\nsections of content within the same contextual view, allowing users to access\nmultiple related pages or views without navigating away from the main screen.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=8571-34966&m=dev)\n[React Aria Docs](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n\n## Tabs vs TabNav\n\n`Tabs` and `TabNav` look identical but serve fundamentally different purposes.\n\n| | `Tabs` | `TabNav` |\n|---|---|---|\n| **Use case** | Switching content panels within a page | Page-level navigation between routes |\n| **HTML semantics** | `role=\"tablist\"` + `role=\"tab\"` | `<nav>` + `<a>` |\n| **Active state** | `aria-selected=\"true\"` | `aria-current=\"page\"` |\n| **Keyboard** | Arrow keys (roving tabindex) | Sequential Tab key |\n| **Content** | Owns associated `TabPanel` | No panel — content managed by router |\n| **URL** | Optional (`href` prop) | Always (`href` required) |\n\nBoth `Tabs` and `TabNav` support client-side routing — the deciding factor is whether the component owns its content panels or delegates to the router. Use `Tabs` when the content lives alongside the tab list. Use [`TabNav`](/components/navigation/tab-nav) when each item navigates to a different route and the router manages what's rendered.\n\n## Variables\n\nGet familiar with the features.\n\n### Direction for tabs\n\nTabs can be oriented horizontally or vertically to suit different layout\nrequirements.\n\n#### Horizontal tabs\n\nOrganize and display distinct, related sections of content immediately beneath a\ncommon navigation bar or header, serving primarily to group content contextually\nwhile conserving vertical screen space.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Large</Text>\n <Tabs.Root size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Medium</Text>\n <Tabs.Root size=\"md\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Small</Text>\n <Tabs.Root size=\"sm\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n#### Vertical tabs\n\nMaximize horizontal reading space and accommodate a larger number of\nconsistently visible options than traditional horizontal tabs allow.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"1000\">\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical left</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical right</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n",
|
|
138
|
+
"mdx": "\n## Overview\n\nTabs are a navigational component used to organize and switch between different\nsections of content within the same contextual view, allowing users to access\nmultiple related pages or views without navigating away from the main screen.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system?node-id=8571-34966&m=dev)\n[React Aria Docs](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n\n## Tabs vs TabNav\n\n`Tabs` and `TabNav` look identical but serve fundamentally different purposes.\n\n| | `Tabs` | `TabNav` |\n|---|---|---|\n| **Use case** | Switching content panels within a page | Page-level navigation between routes |\n| **HTML semantics** | `role=\"tablist\"` + `role=\"tab\"` | `<nav>` + `<a>` |\n| **Active state** | `aria-selected=\"true\"` | `aria-current=\"page\"` |\n| **Keyboard** | Arrow keys (roving tabindex) | Sequential Tab key |\n| **Content** | Owns associated `TabPanel` | No panel — content managed by router |\n| **URL** | Optional (`href` prop) | Always (`href` required) |\n\nBoth `Tabs` and `TabNav` support client-side routing — the deciding factor is whether the component owns its content panels or delegates to the router. Use `Tabs` when the content lives alongside the tab list. Use [`TabNav`](/components/navigation/tab-nav) when each item navigates to a different route and the router manages what's rendered.\n\n## Variables\n\nGet familiar with the features.\n\n### Direction for tabs\n\nTabs can be oriented horizontally or vertically to suit different layout\nrequirements.\n\n#### Horizontal tabs\n\nOrganize and display distinct, related sections of content immediately beneath a\ncommon navigation bar or header, serving primarily to group content contextually\nwhile conserving vertical screen space.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Large</Text>\n <Tabs.Root size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Medium</Text>\n <Tabs.Root size=\"md\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box>\n <Text fontWeight=\"bold\" mb=\"300\">Small</Text>\n <Tabs.Root size=\"sm\">\n <Tabs.List>\n <Tabs.Tab id=\"general\">General info</Tabs.Tab>\n <Tabs.Tab id=\"history\">History</Tabs.Tab>\n <Tabs.Tab id=\"subscription\">Subscription</Tabs.Tab>\n <Tabs.Tab id=\"downloads\">Downloads</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels pt=\"400\">\n <Tabs.Panel id=\"general\">General information content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"history\">History content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"subscription\">Subscription content goes here.</Tabs.Panel>\n <Tabs.Panel id=\"downloads\">Downloads content goes here.</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n#### Vertical tabs\n\nMaximize horizontal reading space and accommodate a larger number of\nconsistently visible options than traditional horizontal tabs allow.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"1000\">\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical left</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n\n <Box flex=\"1\">\n <Text fontWeight=\"bold\" mb=\"300\">Vertical right</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\">\n <Tabs.List>\n <Tabs.Tab id=\"foryou\">\n <Icons.SentimentSatisfied />\n For you\n </Tabs.Tab>\n <Tabs.Tab id=\"recent\">\n <Icons.Schedule />\n Recent\n </Tabs.Tab>\n <Tabs.Tab id=\"starred\">\n <Icons.Star />\n Starred\n </Tabs.Tab>\n <Tabs.Tab id=\"apps\">\n <Icons.Apps />\n Apps\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"foryou\">For you content</Tabs.Panel>\n <Tabs.Panel id=\"recent\">Recent content</Tabs.Panel>\n <Tabs.Panel id=\"starred\">Starred content</Tabs.Panel>\n <Tabs.Panel id=\"apps\">Apps content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Box>\n </Stack>\n)\n```\n\n### Variants\n\nTabs ships with three visual variants (the same set as `TabNav`): `line`\n(default), `rounded`, and `pill`. The active marker always slides between tabs as\nthe selection changes, rather than snapping — a thin bar on the active tab's edge\nfor `line` (an underline when horizontal, an inner side bar when vertical), or a\nfilled highlight for `rounded`/`pill`. The slide is decorative (it does not\naffect selection, focus, or keyboard behavior) and automatically respects the\nuser's reduced-motion preference (snapping instead of sliding).\n\n```jsx live\nconst App = () => {\n const tabs = [\n { id: 'home', tabLabel: 'Home', panelContent: 'Home content' },\n { id: 'about', tabLabel: 'About', panelContent: 'About content' },\n { id: 'contact', tabLabel: 'Contact', panelContent: 'Contact content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"800\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Line</Text>\n <Tabs.Root variant=\"line\" tabs={tabs} />\n </Stack>\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Rounded</Text>\n <Tabs.Root variant=\"rounded\" tabs={tabs} />\n </Stack>\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"bold\">Pill</Text>\n <Tabs.Root variant=\"pill\" tabs={tabs} />\n </Stack>\n </Stack>\n )\n}\n```\n",
|
|
128
139
|
"toc": [
|
|
129
140
|
{
|
|
130
141
|
"value": "Overview",
|
|
@@ -201,6 +212,17 @@
|
|
|
201
212
|
2
|
|
202
213
|
],
|
|
203
214
|
"parent": "root"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"value": "Variants",
|
|
218
|
+
"href": "#variants",
|
|
219
|
+
"depth": 3,
|
|
220
|
+
"numbering": [
|
|
221
|
+
1,
|
|
222
|
+
3,
|
|
223
|
+
2
|
|
224
|
+
],
|
|
225
|
+
"parent": "root"
|
|
204
226
|
}
|
|
205
227
|
]
|
|
206
228
|
},
|
|
@@ -231,7 +253,7 @@
|
|
|
231
253
|
]
|
|
232
254
|
},
|
|
233
255
|
"dev": {
|
|
234
|
-
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Tabs, type TabsProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe Tabs component supports two approaches depending on your needs.\n\nFor maximum control over tab structure and content, use the **Compound API** with manual composition:\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">First Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Second Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Third Tab</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>Content for the first tab.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>Content for the second tab.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Content for the third tab.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\nFor data-driven tabs that render from an array, use the **Dynamic rendering** approach:\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n {\n id: 'tab1',\n tabLabel: 'First Tab',\n panelContent: <Text>Content for the first tab.</Text>,\n },\n {\n id: 'tab2',\n tabLabel: 'Second Tab',\n panelContent: <Text>Content for the second tab.</Text>,\n },\n {\n id: 'tab3',\n tabLabel: 'Third Tab',\n panelContent: <Text>Content for the third tab.</Text>,\n },\n ];\n\n return <Tabs.Root tabs={tabs} tabListAriaLabel=\"Tab sections\" />;\n};\n```\n\nWhen using the `tabs` prop, provide a `tabListAriaLabel` for accessibility. This label is applied to the tab list element for screen readers.\n\n## Usage examples\n\n### Size variants\n\nThe Tabs component supports three size variants: `sm`, `md` (default), and `lg`.\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'Overview', panelContent: 'Overview content' },\n { id: '2', tabLabel: 'Details', panelContent: 'Details content' },\n { id: '3', tabLabel: 'Settings', panelContent: 'Settings content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Small</Text>\n <Tabs.Root size=\"sm\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Medium (default)</Text>\n <Tabs.Root size=\"md\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Large</Text>\n <Tabs.Root size=\"lg\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Visual variants\n\nThe Tabs component offers two visual styles:\n- **line** (default) - Tabs with an underline indicator\n- **pills** - Tabs with a pill-shaped background when active\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'Dashboard', panelContent: 'Dashboard view' },\n { id: '2', tabLabel: 'Analytics', panelContent: 'Analytics view' },\n { id: '3', tabLabel: 'Reports', panelContent: 'Reports view' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Line variant (default)</Text>\n <Tabs.Root variant=\"line\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Pills variant</Text>\n <Tabs.Root variant=\"pills\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Orientation\n\nTabs can be displayed horizontally or vertically using the `orientation` prop.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal (default)</Text>\n <Tabs.Root orientation=\"horizontal\">\n <Tabs.List>\n <Tabs.Tab id=\"home\">Home</Tabs.Tab>\n <Tabs.Tab id=\"about\">About</Tabs.Tab>\n <Tabs.Tab id=\"contact\">Contact</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"home\">\n <Text>Welcome to our homepage!</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"about\">\n <Text>Learn more about us.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"contact\">\n <Text>Get in touch with us.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical</Text>\n <Tabs.Root orientation=\"vertical\">\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"security\">Security</Tabs.Tab>\n <Tabs.Tab id=\"notifications\">Notifications</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">\n <Text>Manage your profile settings.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"security\">\n <Text>Configure security options.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"notifications\">\n <Text>Adjust notification preferences.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Placement\n\nThe `placement` prop controls where the tab list appears relative to the panels:\n- **horizontal orientation**: `start` (left-aligned) or `end` (right-aligned)\n- **vertical orientation**: `start` (tabs on left) or `end` (tabs on right)\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'First', panelContent: 'First panel content' },\n { id: '2', tabLabel: 'Second', panelContent: 'Second panel content' },\n { id: '3', tabLabel: 'Third', panelContent: 'Third panel content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal with placement=\"start\"</Text>\n <Tabs.Root orientation=\"horizontal\" placement=\"start\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal with placement=\"end\"</Text>\n <Tabs.Root orientation=\"horizontal\" placement=\"end\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical with placement=\"start\" (tabs on left)</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical with placement=\"end\" (tabs on right)</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Disabled tabs\n\nIndividual tabs can be disabled using the `disabledKeys` prop on `Tabs.Root`, or the `isDisabled` prop on individual `Tabs.Tab` components.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Disabled via disabledKeys prop</Text>\n <Tabs.Root disabledKeys={['tab2']}>\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">Active Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Disabled Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Another Active Tab</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>First tab content is accessible.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>This content cannot be accessed.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Third tab content is accessible.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Disabled via isDisabled prop</Text>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"enabled1\">Available</Tabs.Tab>\n <Tabs.Tab id=\"disabled1\" isDisabled>\n Unavailable\n </Tabs.Tab>\n <Tabs.Tab id=\"enabled2\">Available</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"enabled1\">\n <Text>This tab is enabled.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"disabled1\">\n <Text>This tab is disabled.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"enabled2\">\n <Text>This tab is also enabled.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Dynamic tabs with tabs prop\n\nFor dynamic content, pass an array of tab configurations to the `tabs` prop. This is useful when tab data comes from an API or changes at runtime.\n\n```jsx live-dev\nconst App = () => {\n const [tabs, setTabs] = useState([\n {\n id: '1',\n tabLabel: 'Introduction',\n panelContent: 'Welcome to our product documentation.',\n },\n {\n id: '2',\n tabLabel: 'Getting Started',\n panelContent: 'Learn how to get started with our product.',\n },\n {\n id: '3',\n tabLabel: 'API Reference',\n panelContent: 'Explore our comprehensive API documentation.',\n },\n ]);\n\n const addTab = () => {\n const newId = String(tabs.length + 1);\n setTabs([\n ...tabs,\n {\n id: newId,\n tabLabel: `Tab ${newId}`,\n panelContent: `Dynamic content for tab ${newId}`,\n },\n ]);\n };\n\n const removeLastTab = () => {\n if (tabs.length > 1) {\n setTabs(tabs.slice(0, -1));\n }\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"300\">\n <Button onClick={addTab} size=\"md\">\n Add Tab\n </Button>\n <Button onClick={removeLastTab} size=\"md\" variant=\"outline\">\n Remove Last Tab\n </Button>\n </Stack>\n\n <Tabs.Root tabs={tabs} />\n </Stack>\n );\n};\n```\n\n### Manual composition with compound API\n\nUse the compound API for more control over tab and panel rendering, including custom content and complex layouts.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root variant=\"pills\">\n <Tabs.List>\n <Tabs.Tab id=\"overview\">\n <Icons.Home />\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"analytics\">\n <Icons.BarChart />\n Analytics\n </Tabs.Tab>\n <Tabs.Tab id=\"settings\">\n <Icons.Settings />\n Settings\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Dashboard Overview\n </Heading>\n <Text>\n View your key metrics and recent activity at a glance.\n </Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"analytics\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Analytics & Reports\n </Heading>\n <Text>\n Dive deep into your data with comprehensive analytics tools.\n </Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"settings\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Configuration Settings\n </Heading>\n <Text>\n Customize your experience with advanced settings options.\n </Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Uncontrolled mode\n\nBy default, Tabs operates in uncontrolled mode where it manages its own state. Use `defaultSelectedKey` to set the initially selected tab.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root defaultSelectedKey=\"tab2\">\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">Tab 1</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Tab 2 (Initially Selected)</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Tab 3</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>First tab content</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>Second tab content - selected by default</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Third tab content</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Controlled mode\n\nFor full control over tab selection, use the `selectedKey` and `onSelectionChange` props.\n\n```jsx live-dev\nconst App = () => {\n const [selectedTab, setSelectedTab] = useState<TabsProps['selectedKey']>('profile');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Text>\n Currently selected tab: <strong>{String(selectedTab)}</strong>\n </Text>\n\n <Tabs.Root\n selectedKey={selectedTab}\n onSelectionChange={setSelectedTab}\n >\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"account\">Account</Tabs.Tab>\n <Tabs.Tab id=\"preferences\">Preferences</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">\n <Text>Manage your profile information</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"account\">\n <Text>View and edit account settings</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"preferences\">\n <Text>Customize your preferences</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n\n <Stack direction=\"row\" gap=\"300\">\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('profile')}\n variant={selectedTab === 'profile' ? 'solid' : 'outline'}\n >\n Go to Profile\n </Button>\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('account')}\n variant={selectedTab === 'account' ? 'solid' : 'outline'}\n >\n Go to Account\n </Button>\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('preferences')}\n variant={selectedTab === 'preferences' ? 'solid' : 'outline'}\n >\n Go to Preferences\n </Button>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Router-integrated tabs\n\nTabs can render as links for URL-driven navigation by providing an `href` prop. When used with a `NimbusProvider` router configuration, clicking a tab calls the router's navigate function instead of triggering a full page reload.\n\n```jsx live-dev\nconst App = () => {\n // In a real app, this would be your router's navigate function\n // e.g. React Router's useNavigate() or Next.js router.push()\n const router = {\n navigate: (href) => {\n alert(`Navigating to: ${href}`);\n },\n };\n\n return (\n <NimbusProvider router={router}>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\" href=\"/products/overview\">\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"details\" href=\"/products/details\">\n Details\n </Tabs.Tab>\n <Tabs.Tab id=\"reviews\" href=\"/products/reviews\">\n Reviews\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">\n <Text>Product overview content.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"details\">\n <Text>Product detail specifications.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"reviews\">\n <Text>Customer reviews and ratings.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n};\n```\n\nLink tabs also work with the simplified `tabs` prop API:\n\n```jsx live-dev\nconst App = () => {\n const router = {\n navigate: (href) => {\n alert(`Navigating to: ${href}`);\n },\n };\n\n const tabs = [\n { id: 'overview', tabLabel: 'Overview', href: '/products/overview', panelContent: 'Product overview content.' },\n { id: 'details', tabLabel: 'Details', href: '/products/details', panelContent: 'Product detail specifications.' },\n { id: 'reviews', tabLabel: 'Reviews', href: '/products/reviews', panelContent: 'Customer reviews and ratings.' },\n ];\n\n return (\n <NimbusProvider router={router}>\n <Tabs.Root tabs={tabs} tabListAriaLabel=\"Product sections\" />\n </NimbusProvider>\n );\n};\n```\n\nAdditional link props like `target` and `rel` can be used for external links:\n\n```tsx\n<Tabs.Tab id=\"docs\" href=\"https://docs.example.com\" target=\"_blank\" rel=\"noopener noreferrer\">\n External Docs\n</Tabs.Tab>\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Tabs component follows WCAG 2.1 AA guidelines and implements the WAI-ARIA tabs pattern.\n\n#### Role\n\n- `Tabs.Root` contains the tablist and tabpanels\n- `Tabs.List` has `role=\"tablist\"`\n- `Tabs.Tab` has `role=\"tab\"` with `aria-selected` indicating active state\n- `Tabs.Panel` has `role=\"tabpanel\"` and is associated with its tab via `aria-labelledby`\n\n#### Labeling\n\n- Each tab must have an `id` prop that matches its corresponding panel\n- Tab content should be descriptive and concise\n- Complex tab labels can include icons and text\n- When using the `tabs` prop API, provide `tabListAriaLabel` to label the tab list for screen readers\n- When using the compound API, apply `aria-label` directly to `Tabs.List`\n\n#### Keyboard navigation\n\n| Key | Action |\n|-----|--------|\n| `Tab` | Move focus into and out of the tab list |\n| `ArrowRight` / `ArrowDown` | Move focus to the next tab (horizontal/vertical) |\n| `ArrowLeft` / `ArrowUp` | Move focus to the previous tab (horizontal/vertical) |\n| `Home` | Move focus to the first tab |\n| `End` | Move focus to the last tab |\n| `Enter` / `Space` | Activate the focused tab (when `keyboardActivation=\"manual\"`) |\n\n#### Persistent ID\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"user-settings-tabs\";\n\nexport const SettingsTabs = () => (\n <Tabs.Root id={PERSISTENT_ID}>\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"security\">Security</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">Profile content</Tabs.Panel>\n <Tabs.Panel id=\"security\">Security content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n);\n```\n\n## API reference\n\n<PropsTable id=\"Tabs\" />\n\n## Common patterns\n\n### Tab content with icons\n\nCombine icons with text labels for more expressive tabs.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root variant=\"pills\" size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"dashboard\">\n <Icons.Dashboard />\n Dashboard\n </Tabs.Tab>\n <Tabs.Tab id=\"reports\">\n <Icons.Assessment />\n Reports\n </Tabs.Tab>\n <Tabs.Tab id=\"settings\">\n <Icons.Settings />\n Settings\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"dashboard\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Dashboard content with key metrics and visualizations.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"reports\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Generate and view comprehensive reports.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"settings\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Configure application settings and preferences.</Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Tabs with badges\n\nDisplay notification counts or status indicators alongside tab labels using badges.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"inbox\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Inbox</Text>\n <Badge colorPalette=\"primary\" size=\"xs\">12</Badge>\n </Stack>\n </Tabs.Tab>\n <Tabs.Tab id=\"drafts\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Drafts</Text>\n <Badge colorPalette=\"neutral\" size=\"xs\">3</Badge>\n </Stack>\n </Tabs.Tab>\n <Tabs.Tab id=\"sent\">\n <Text>Sent</Text>\n </Tabs.Tab>\n <Tabs.Tab id=\"spam\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Spam</Text>\n <Badge colorPalette=\"critical\" size=\"xs\">2</Badge>\n </Stack>\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"inbox\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>You have 12 new messages in your inbox.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"drafts\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>You have 3 draft messages.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"sent\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>View your sent messages.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"spam\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>2 messages marked as spam.</Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tabs within your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Usage\n\nHow to render tabs using the compound composition API\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Basic usage\", () => {\n it(\"renders tabs with compound composition\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tablist\")).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Overview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tabpanel\")).toHaveTextContent(\"Overview content\");\n });\n\n it(\"renders tabs with the simplified tabs prop\", () => {\n const tabs = [\n {\n id: \"overview\",\n tabLabel: \"Overview\",\n panelContent: \"Overview content\",\n },\n { id: \"details\", tabLabel: \"Details\", panelContent: \"Details content\" },\n ];\n\n render(\n <NimbusProvider>\n <Tabs.Root tabs={tabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Overview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toBeInTheDocument();\n });\n});\n```\n\n### Controlled Tabs\n\nHow to control the active tab from external state (e.g., URL parameters)\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Controlled tabs\", () => {\n it(\"controls the selected tab via selectedKey and onSelectionChange\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n const ControlledExample = () => {\n const [selectedKey, setSelectedKey] = React.useState<string | number>(\n \"overview\"\n );\n\n return (\n <Tabs.Root\n selectedKey={selectedKey}\n onSelectionChange={(key) => {\n setSelectedKey(key);\n handleSelectionChange(key);\n }}\n >\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n };\n\n render(\n <NimbusProvider>\n <ControlledExample />\n </NimbusProvider>\n );\n\n await user.click(screen.getByRole(\"tab\", { name: \"Details\" }));\n\n expect(handleSelectionChange).toHaveBeenCalledWith(\"details\");\n expect(screen.getByRole(\"tabpanel\")).toHaveTextContent(\"Details content\");\n });\n\n it(\"sets the initial tab with defaultSelectedKey\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root defaultSelectedKey=\"details\">\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toHaveAttribute(\n \"aria-selected\",\n \"true\"\n );\n });\n});\n```\n\n### Router Integration\n\nHow to use tabs as links for URL-driven navigation with a client-side router\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Router integration\", () => {\n it(\"renders link tabs that navigate via the router\", async () => {\n const user = userEvent.setup();\n const navigate = vi.fn();\n\n const router: NimbusRouterConfig = { navigate };\n\n render(\n <NimbusProvider router={router}>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\" href=\"/products?tab=overview\">\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"details\" href=\"/products?tab=details\">\n Details\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n // Link tabs render as anchor elements\n const detailsTab = screen.getByRole(\"tab\", { name: \"Details\" });\n expect(detailsTab.tagName).toBe(\"A\");\n expect(detailsTab).toHaveAttribute(\"href\", \"/products?tab=details\");\n\n // Clicking calls the router navigate function\n await user.click(detailsTab);\n expect(navigate).toHaveBeenCalledWith(\"/products?tab=details\", undefined);\n });\n});\n```\n\n### Disabled Tabs\n\nHow to disable individual tabs or sets of tabs\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Disabled tabs\", () => {\n it(\"disables specific tabs via disabledKeys on the root\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root disabledKeys={[\"details\"]}>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n const disabledTab = screen.getByRole(\"tab\", { name: \"Details\" });\n expect(disabledTab).toHaveAttribute(\"aria-disabled\", \"true\");\n });\n\n it(\"disables individual tabs via the isDisabled prop\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\" isDisabled>\n Details\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toHaveAttribute(\n \"aria-disabled\",\n \"true\"\n );\n });\n});\n```\n\n### Dynamic Tabs\n\nHow to render tabs from dynamic data and handle updates\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Dynamic tabs\", () => {\n it(\"renders tabs from a dynamic array and updates when data changes\", () => {\n const initialTabs = [\n { id: \"1\", tabLabel: \"First\", panelContent: \"First content\" },\n { id: \"2\", tabLabel: \"Second\", panelContent: \"Second content\" },\n ];\n\n const { rerender } = render(\n <NimbusProvider>\n <Tabs.Root tabs={initialTabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"First\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Second\" })).toBeInTheDocument();\n\n const updatedTabs = [\n ...initialTabs,\n { id: \"3\", tabLabel: \"Third\", panelContent: \"Third content\" },\n ];\n\n rerender(\n <NimbusProvider>\n <Tabs.Root tabs={updatedTabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Third\" })).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Tabs Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tabs--docs)\n- [React Aria Tabs Documentation](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n- [WAI-ARIA Tabs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)\n",
|
|
256
|
+
"mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Tabs, type TabsProps } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe Tabs component supports two approaches depending on your needs.\n\nFor maximum control over tab structure and content, use the **Compound API** with manual composition:\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">First Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Second Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Third Tab</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>Content for the first tab.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>Content for the second tab.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Content for the third tab.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\nFor data-driven tabs that render from an array, use the **Dynamic rendering** approach:\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n {\n id: 'tab1',\n tabLabel: 'First Tab',\n panelContent: <Text>Content for the first tab.</Text>,\n },\n {\n id: 'tab2',\n tabLabel: 'Second Tab',\n panelContent: <Text>Content for the second tab.</Text>,\n },\n {\n id: 'tab3',\n tabLabel: 'Third Tab',\n panelContent: <Text>Content for the third tab.</Text>,\n },\n ];\n\n return <Tabs.Root tabs={tabs} tabListAriaLabel=\"Tab sections\" />;\n};\n```\n\nWhen using the `tabs` prop, provide a `tabListAriaLabel` for accessibility. This label is applied to the tab list element for screen readers.\n\n## Usage examples\n\n### Size variants\n\nThe Tabs component supports three size variants: `sm`, `md` (default), and `lg`.\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'Overview', panelContent: 'Overview content' },\n { id: '2', tabLabel: 'Details', panelContent: 'Details content' },\n { id: '3', tabLabel: 'Settings', panelContent: 'Settings content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Small</Text>\n <Tabs.Root size=\"sm\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Medium (default)</Text>\n <Tabs.Root size=\"md\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Large</Text>\n <Tabs.Root size=\"lg\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Visual variants\n\nThe Tabs component offers three visual styles (the same set as `TabNav`):\n- **line** (default) - a bar marking the active tab (an underline when horizontal, an inner side bar when vertical)\n- **rounded** - a soft rounded-rect highlight behind the active tab\n- **pill** - a fully-rounded capsule highlight behind the active tab\n\nThe `rounded` and `pill` highlights are themeable via `colorPalette` (defaulting to `primary`). The legacy `pills` (→ `pill`) name is accepted as a deprecated alias.\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'Dashboard', panelContent: 'Dashboard view' },\n { id: '2', tabLabel: 'Analytics', panelContent: 'Analytics view' },\n { id: '3', tabLabel: 'Reports', panelContent: 'Reports view' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Line variant (default)</Text>\n <Tabs.Root variant=\"line\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Rounded variant</Text>\n <Tabs.Root variant=\"rounded\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Pill variant</Text>\n <Tabs.Root variant=\"pill\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Animated indicator\n\nThe active marker always slides between tabs as the selection changes, instead\nof snapping — no prop required. The indicator adapts to the resolved variant,\norientation, and placement: a thin bar on the active tab's bottom edge\n(horizontal `line`), its inner side edge (vertical `line`, per `placement`), or a\nfilled highlight (`rounded` / `pill`).\n\nThe indicator is decorative (`aria-hidden`, non-focusable), so `aria-selected`,\nfocus rings, and keyboard navigation are unchanged. The slide is automatically\ndisabled under `prefers-reduced-motion: reduce` — the marker snaps into place.\n(There is no per-instance opt-out; disabling is a global, OS-level\nreduced-motion concern.)\n\n### Orientation\n\nTabs can be displayed horizontally or vertically using the `orientation` prop.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal (default)</Text>\n <Tabs.Root orientation=\"horizontal\">\n <Tabs.List>\n <Tabs.Tab id=\"home\">Home</Tabs.Tab>\n <Tabs.Tab id=\"about\">About</Tabs.Tab>\n <Tabs.Tab id=\"contact\">Contact</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"home\">\n <Text>Welcome to our homepage!</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"about\">\n <Text>Learn more about us.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"contact\">\n <Text>Get in touch with us.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical</Text>\n <Tabs.Root orientation=\"vertical\">\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"security\">Security</Tabs.Tab>\n <Tabs.Tab id=\"notifications\">Notifications</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">\n <Text>Manage your profile settings.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"security\">\n <Text>Configure security options.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"notifications\">\n <Text>Adjust notification preferences.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Placement\n\nThe `placement` prop controls where the tab list appears relative to the panels:\n- **horizontal orientation**: `start` (left-aligned) or `end` (right-aligned)\n- **vertical orientation**: `start` (tabs on left) or `end` (tabs on right)\n\n```jsx live-dev\nconst App = () => {\n const tabs = [\n { id: '1', tabLabel: 'First', panelContent: 'First panel content' },\n { id: '2', tabLabel: 'Second', panelContent: 'Second panel content' },\n { id: '3', tabLabel: 'Third', panelContent: 'Third panel content' },\n ];\n\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal with placement=\"start\"</Text>\n <Tabs.Root orientation=\"horizontal\" placement=\"start\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Horizontal with placement=\"end\"</Text>\n <Tabs.Root orientation=\"horizontal\" placement=\"end\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical with placement=\"start\" (tabs on left)</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"start\" tabs={tabs} />\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Vertical with placement=\"end\" (tabs on right)</Text>\n <Tabs.Root orientation=\"vertical\" placement=\"end\" tabs={tabs} />\n </Stack>\n </Stack>\n );\n};\n```\n\n### Disabled tabs\n\nIndividual tabs can be disabled using the `disabledKeys` prop on `Tabs.Root`, or the `isDisabled` prop on individual `Tabs.Tab` components.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Stack direction=\"column\" gap=\"600\">\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Disabled via disabledKeys prop</Text>\n <Tabs.Root disabledKeys={['tab2']}>\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">Active Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Disabled Tab</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Another Active Tab</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>First tab content is accessible.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>This content cannot be accessed.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Third tab content is accessible.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n\n <Stack direction=\"column\" gap=\"200\">\n <Text fontWeight=\"500\">Disabled via isDisabled prop</Text>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"enabled1\">Available</Tabs.Tab>\n <Tabs.Tab id=\"disabled1\" isDisabled>\n Unavailable\n </Tabs.Tab>\n <Tabs.Tab id=\"enabled2\">Available</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"enabled1\">\n <Text>This tab is enabled.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"disabled1\">\n <Text>This tab is disabled.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"enabled2\">\n <Text>This tab is also enabled.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Dynamic tabs with tabs prop\n\nFor dynamic content, pass an array of tab configurations to the `tabs` prop. This is useful when tab data comes from an API or changes at runtime.\n\n```jsx live-dev\nconst App = () => {\n const [tabs, setTabs] = useState([\n {\n id: '1',\n tabLabel: 'Introduction',\n panelContent: 'Welcome to our product documentation.',\n },\n {\n id: '2',\n tabLabel: 'Getting Started',\n panelContent: 'Learn how to get started with our product.',\n },\n {\n id: '3',\n tabLabel: 'API Reference',\n panelContent: 'Explore our comprehensive API documentation.',\n },\n ]);\n\n const addTab = () => {\n const newId = String(tabs.length + 1);\n setTabs([\n ...tabs,\n {\n id: newId,\n tabLabel: `Tab ${newId}`,\n panelContent: `Dynamic content for tab ${newId}`,\n },\n ]);\n };\n\n const removeLastTab = () => {\n if (tabs.length > 1) {\n setTabs(tabs.slice(0, -1));\n }\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"300\">\n <Button onClick={addTab} size=\"md\">\n Add Tab\n </Button>\n <Button onClick={removeLastTab} size=\"md\" variant=\"outline\">\n Remove Last Tab\n </Button>\n </Stack>\n\n <Tabs.Root tabs={tabs} />\n </Stack>\n );\n};\n```\n\n### Manual composition with compound API\n\nUse the compound API for more control over tab and panel rendering, including custom content and complex layouts.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root variant=\"pill\">\n <Tabs.List>\n <Tabs.Tab id=\"overview\">\n <Icons.Home />\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"analytics\">\n <Icons.BarChart />\n Analytics\n </Tabs.Tab>\n <Tabs.Tab id=\"settings\">\n <Icons.Settings />\n Settings\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Dashboard Overview\n </Heading>\n <Text>\n View your key metrics and recent activity at a glance.\n </Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"analytics\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Analytics & Reports\n </Heading>\n <Text>\n Dive deep into your data with comprehensive analytics tools.\n </Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"settings\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Heading as=\"h3\" fontSize=\"500\">\n Configuration Settings\n </Heading>\n <Text>\n Customize your experience with advanced settings options.\n </Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Uncontrolled mode\n\nBy default, Tabs operates in uncontrolled mode where it manages its own state. Use `defaultSelectedKey` to set the initially selected tab.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root defaultSelectedKey=\"tab2\">\n <Tabs.List>\n <Tabs.Tab id=\"tab1\">Tab 1</Tabs.Tab>\n <Tabs.Tab id=\"tab2\">Tab 2 (Initially Selected)</Tabs.Tab>\n <Tabs.Tab id=\"tab3\">Tab 3</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"tab1\">\n <Text>First tab content</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab2\">\n <Text>Second tab content - selected by default</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"tab3\">\n <Text>Third tab content</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Controlled mode\n\nFor full control over tab selection, use the `selectedKey` and `onSelectionChange` props.\n\n```jsx live-dev\nconst App = () => {\n const [selectedTab, setSelectedTab] = useState<TabsProps['selectedKey']>('profile');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Text>\n Currently selected tab: <strong>{String(selectedTab)}</strong>\n </Text>\n\n <Tabs.Root\n selectedKey={selectedTab}\n onSelectionChange={setSelectedTab}\n >\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"account\">Account</Tabs.Tab>\n <Tabs.Tab id=\"preferences\">Preferences</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">\n <Text>Manage your profile information</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"account\">\n <Text>View and edit account settings</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"preferences\">\n <Text>Customize your preferences</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n\n <Stack direction=\"row\" gap=\"300\">\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('profile')}\n variant={selectedTab === 'profile' ? 'solid' : 'outline'}\n >\n Go to Profile\n </Button>\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('account')}\n variant={selectedTab === 'account' ? 'solid' : 'outline'}\n >\n Go to Account\n </Button>\n <Button\n size=\"md\"\n onClick={() => setSelectedTab('preferences')}\n variant={selectedTab === 'preferences' ? 'solid' : 'outline'}\n >\n Go to Preferences\n </Button>\n </Stack>\n </Stack>\n );\n};\n```\n\n### Router-integrated tabs\n\nTabs can render as links for URL-driven navigation by providing an `href` prop. When used with a `NimbusProvider` router configuration, clicking a tab calls the router's navigate function instead of triggering a full page reload.\n\n```jsx live-dev\nconst App = () => {\n // In a real app, this would be your router's navigate function\n // e.g. React Router's useNavigate() or Next.js router.push()\n const router = {\n navigate: (href) => {\n alert(`Navigating to: ${href}`);\n },\n };\n\n return (\n <NimbusProvider router={router}>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\" href=\"/products/overview\">\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"details\" href=\"/products/details\">\n Details\n </Tabs.Tab>\n <Tabs.Tab id=\"reviews\" href=\"/products/reviews\">\n Reviews\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">\n <Text>Product overview content.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"details\">\n <Text>Product detail specifications.</Text>\n </Tabs.Panel>\n <Tabs.Panel id=\"reviews\">\n <Text>Customer reviews and ratings.</Text>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n};\n```\n\nLink tabs also work with the simplified `tabs` prop API:\n\n```jsx live-dev\nconst App = () => {\n const router = {\n navigate: (href) => {\n alert(`Navigating to: ${href}`);\n },\n };\n\n const tabs = [\n { id: 'overview', tabLabel: 'Overview', href: '/products/overview', panelContent: 'Product overview content.' },\n { id: 'details', tabLabel: 'Details', href: '/products/details', panelContent: 'Product detail specifications.' },\n { id: 'reviews', tabLabel: 'Reviews', href: '/products/reviews', panelContent: 'Customer reviews and ratings.' },\n ];\n\n return (\n <NimbusProvider router={router}>\n <Tabs.Root tabs={tabs} tabListAriaLabel=\"Product sections\" />\n </NimbusProvider>\n );\n};\n```\n\nAdditional link props like `target` and `rel` can be used for external links:\n\n```tsx\n<Tabs.Tab id=\"docs\" href=\"https://docs.example.com\" target=\"_blank\" rel=\"noopener noreferrer\">\n External Docs\n</Tabs.Tab>\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Tabs component follows WCAG 2.1 AA guidelines and implements the WAI-ARIA tabs pattern.\n\n#### Role\n\n- `Tabs.Root` contains the tablist and tabpanels\n- `Tabs.List` has `role=\"tablist\"`\n- `Tabs.Tab` has `role=\"tab\"` with `aria-selected` indicating active state\n- `Tabs.Panel` has `role=\"tabpanel\"` and is associated with its tab via `aria-labelledby`\n\n#### Labeling\n\n- Each tab must have an `id` prop that matches its corresponding panel\n- Tab content should be descriptive and concise\n- Complex tab labels can include icons and text\n- When using the `tabs` prop API, provide `tabListAriaLabel` to label the tab list for screen readers\n- When using the compound API, apply `aria-label` directly to `Tabs.List`\n\n#### Keyboard navigation\n\n| Key | Action |\n|-----|--------|\n| `Tab` | Move focus into and out of the tab list |\n| `ArrowRight` / `ArrowDown` | Move focus to the next tab (horizontal/vertical) |\n| `ArrowLeft` / `ArrowUp` | Move focus to the previous tab (horizontal/vertical) |\n| `Home` | Move focus to the first tab |\n| `End` | Move focus to the last tab |\n| `Enter` / `Space` | Activate the focused tab (when `keyboardActivation=\"manual\"`) |\n\n#### Persistent ID\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the component:\n\n```tsx\nconst PERSISTENT_ID = \"user-settings-tabs\";\n\nexport const SettingsTabs = () => (\n <Tabs.Root id={PERSISTENT_ID}>\n <Tabs.List>\n <Tabs.Tab id=\"profile\">Profile</Tabs.Tab>\n <Tabs.Tab id=\"security\">Security</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"profile\">Profile content</Tabs.Panel>\n <Tabs.Panel id=\"security\">Security content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n);\n```\n\n## API reference\n\n<PropsTable id=\"Tabs\" />\n\n## Common patterns\n\n### Tab content with icons\n\nCombine icons with text labels for more expressive tabs.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root variant=\"pill\" size=\"lg\">\n <Tabs.List>\n <Tabs.Tab id=\"dashboard\">\n <Icons.Dashboard />\n Dashboard\n </Tabs.Tab>\n <Tabs.Tab id=\"reports\">\n <Icons.Assessment />\n Reports\n </Tabs.Tab>\n <Tabs.Tab id=\"settings\">\n <Icons.Settings />\n Settings\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"dashboard\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Dashboard content with key metrics and visualizations.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"reports\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Generate and view comprehensive reports.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"settings\">\n <Stack direction=\"column\" gap=\"300\" padding=\"500\">\n <Text>Configure application settings and preferences.</Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n### Tabs with badges\n\nDisplay notification counts or status indicators alongside tab labels using badges.\n\n```jsx live-dev\nconst App = () => {\n return (\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"inbox\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Inbox</Text>\n <Badge colorPalette=\"primary\" size=\"xs\">12</Badge>\n </Stack>\n </Tabs.Tab>\n <Tabs.Tab id=\"drafts\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Drafts</Text>\n <Badge colorPalette=\"neutral\" size=\"xs\">3</Badge>\n </Stack>\n </Tabs.Tab>\n <Tabs.Tab id=\"sent\">\n <Text>Sent</Text>\n </Tabs.Tab>\n <Tabs.Tab id=\"spam\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text>Spam</Text>\n <Badge colorPalette=\"critical\" size=\"xs\">2</Badge>\n </Stack>\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"inbox\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>You have 12 new messages in your inbox.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"drafts\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>You have 3 draft messages.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"sent\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>View your sent messages.</Text>\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel id=\"spam\">\n <Stack direction=\"column\" gap=\"300\" padding=\"400\">\n <Text>2 messages marked as spam.</Text>\n </Stack>\n </Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n};\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tabs within your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Usage\n\nHow to render tabs using the compound composition API\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Basic usage\", () => {\n it(\"renders tabs with compound composition\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tablist\")).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Overview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tabpanel\")).toHaveTextContent(\"Overview content\");\n });\n\n it(\"renders tabs with the simplified tabs prop\", () => {\n const tabs = [\n {\n id: \"overview\",\n tabLabel: \"Overview\",\n panelContent: \"Overview content\",\n },\n { id: \"details\", tabLabel: \"Details\", panelContent: \"Details content\" },\n ];\n\n render(\n <NimbusProvider>\n <Tabs.Root tabs={tabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Overview\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toBeInTheDocument();\n });\n});\n```\n\n### Controlled Tabs\n\nHow to control the active tab from external state (e.g., URL parameters)\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Controlled tabs\", () => {\n it(\"controls the selected tab via selectedKey and onSelectionChange\", async () => {\n const user = userEvent.setup();\n const handleSelectionChange = vi.fn();\n\n const ControlledExample = () => {\n const [selectedKey, setSelectedKey] = React.useState<string | number>(\n \"overview\"\n );\n\n return (\n <Tabs.Root\n selectedKey={selectedKey}\n onSelectionChange={(key) => {\n setSelectedKey(key);\n handleSelectionChange(key);\n }}\n >\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n );\n };\n\n render(\n <NimbusProvider>\n <ControlledExample />\n </NimbusProvider>\n );\n\n await user.click(screen.getByRole(\"tab\", { name: \"Details\" }));\n\n expect(handleSelectionChange).toHaveBeenCalledWith(\"details\");\n expect(screen.getByRole(\"tabpanel\")).toHaveTextContent(\"Details content\");\n });\n\n it(\"sets the initial tab with defaultSelectedKey\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root defaultSelectedKey=\"details\">\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toHaveAttribute(\n \"aria-selected\",\n \"true\"\n );\n });\n});\n```\n\n### Router Integration\n\nHow to use tabs as links for URL-driven navigation with a client-side router\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Router integration\", () => {\n it(\"renders link tabs that navigate via the router\", async () => {\n const user = userEvent.setup();\n const navigate = vi.fn();\n\n const router: NimbusRouterConfig = { navigate };\n\n render(\n <NimbusProvider router={router}>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\" href=\"/products?tab=overview\">\n Overview\n </Tabs.Tab>\n <Tabs.Tab id=\"details\" href=\"/products?tab=details\">\n Details\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n // Link tabs render as anchor elements\n const detailsTab = screen.getByRole(\"tab\", { name: \"Details\" });\n expect(detailsTab.tagName).toBe(\"A\");\n expect(detailsTab).toHaveAttribute(\"href\", \"/products?tab=details\");\n\n // Clicking calls the router navigate function\n await user.click(detailsTab);\n expect(navigate).toHaveBeenCalledWith(\"/products?tab=details\", undefined);\n });\n});\n```\n\n### Disabled Tabs\n\nHow to disable individual tabs or sets of tabs\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Disabled tabs\", () => {\n it(\"disables specific tabs via disabledKeys on the root\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root disabledKeys={[\"details\"]}>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\">Details</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n const disabledTab = screen.getByRole(\"tab\", { name: \"Details\" });\n expect(disabledTab).toHaveAttribute(\"aria-disabled\", \"true\");\n });\n\n it(\"disables individual tabs via the isDisabled prop\", () => {\n render(\n <NimbusProvider>\n <Tabs.Root>\n <Tabs.List>\n <Tabs.Tab id=\"overview\">Overview</Tabs.Tab>\n <Tabs.Tab id=\"details\" isDisabled>\n Details\n </Tabs.Tab>\n </Tabs.List>\n <Tabs.Panels>\n <Tabs.Panel id=\"overview\">Overview content</Tabs.Panel>\n <Tabs.Panel id=\"details\">Details content</Tabs.Panel>\n </Tabs.Panels>\n </Tabs.Root>\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Details\" })).toHaveAttribute(\n \"aria-disabled\",\n \"true\"\n );\n });\n});\n```\n\n### Dynamic Tabs\n\nHow to render tabs from dynamic data and handle updates\n\n```tsx\nimport React from \"react\";\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tabs, NimbusProvider } from \"@commercetools/nimbus\";\nimport type { NimbusRouterConfig } from \"../nimbus-provider/nimbus-provider.types\";\n\ndescribe(\"Tabs - Dynamic tabs\", () => {\n it(\"renders tabs from a dynamic array and updates when data changes\", () => {\n const initialTabs = [\n { id: \"1\", tabLabel: \"First\", panelContent: \"First content\" },\n { id: \"2\", tabLabel: \"Second\", panelContent: \"Second content\" },\n ];\n\n const { rerender } = render(\n <NimbusProvider>\n <Tabs.Root tabs={initialTabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"First\" })).toBeInTheDocument();\n expect(screen.getByRole(\"tab\", { name: \"Second\" })).toBeInTheDocument();\n\n const updatedTabs = [\n ...initialTabs,\n { id: \"3\", tabLabel: \"Third\", panelContent: \"Third content\" },\n ];\n\n rerender(\n <NimbusProvider>\n <Tabs.Root tabs={updatedTabs} />\n </NimbusProvider>\n );\n\n expect(screen.getByRole(\"tab\", { name: \"Third\" })).toBeInTheDocument();\n });\n});\n```\n\n\n## Resources\n\n- [Tabs Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tabs--docs)\n- [React Aria Tabs Documentation](https://react-spectrum.adobe.com/react-aria/Tabs.html)\n- [WAI-ARIA Tabs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/)\n",
|
|
235
257
|
"toc": [
|
|
236
258
|
{
|
|
237
259
|
"value": "Getting started",
|
|
@@ -297,6 +319,17 @@
|
|
|
297
319
|
],
|
|
298
320
|
"parent": "root"
|
|
299
321
|
},
|
|
322
|
+
{
|
|
323
|
+
"value": "Animated indicator",
|
|
324
|
+
"href": "#animated-indicator",
|
|
325
|
+
"depth": 3,
|
|
326
|
+
"numbering": [
|
|
327
|
+
1,
|
|
328
|
+
2,
|
|
329
|
+
3
|
|
330
|
+
],
|
|
331
|
+
"parent": "root"
|
|
332
|
+
},
|
|
300
333
|
{
|
|
301
334
|
"value": "Orientation",
|
|
302
335
|
"href": "#orientation",
|
|
@@ -304,7 +337,7 @@
|
|
|
304
337
|
"numbering": [
|
|
305
338
|
1,
|
|
306
339
|
2,
|
|
307
|
-
|
|
340
|
+
4
|
|
308
341
|
],
|
|
309
342
|
"parent": "root"
|
|
310
343
|
},
|
|
@@ -315,7 +348,7 @@
|
|
|
315
348
|
"numbering": [
|
|
316
349
|
1,
|
|
317
350
|
2,
|
|
318
|
-
|
|
351
|
+
5
|
|
319
352
|
],
|
|
320
353
|
"parent": "root"
|
|
321
354
|
},
|
|
@@ -326,7 +359,7 @@
|
|
|
326
359
|
"numbering": [
|
|
327
360
|
1,
|
|
328
361
|
2,
|
|
329
|
-
|
|
362
|
+
6
|
|
330
363
|
],
|
|
331
364
|
"parent": "root"
|
|
332
365
|
},
|
|
@@ -337,7 +370,7 @@
|
|
|
337
370
|
"numbering": [
|
|
338
371
|
1,
|
|
339
372
|
2,
|
|
340
|
-
|
|
373
|
+
7
|
|
341
374
|
],
|
|
342
375
|
"parent": "root"
|
|
343
376
|
},
|
|
@@ -348,7 +381,7 @@
|
|
|
348
381
|
"numbering": [
|
|
349
382
|
1,
|
|
350
383
|
2,
|
|
351
|
-
|
|
384
|
+
8
|
|
352
385
|
],
|
|
353
386
|
"parent": "root"
|
|
354
387
|
},
|
|
@@ -359,7 +392,7 @@
|
|
|
359
392
|
"numbering": [
|
|
360
393
|
1,
|
|
361
394
|
2,
|
|
362
|
-
|
|
395
|
+
9
|
|
363
396
|
],
|
|
364
397
|
"parent": "root"
|
|
365
398
|
},
|
|
@@ -370,7 +403,7 @@
|
|
|
370
403
|
"numbering": [
|
|
371
404
|
1,
|
|
372
405
|
2,
|
|
373
|
-
|
|
406
|
+
10
|
|
374
407
|
],
|
|
375
408
|
"parent": "root"
|
|
376
409
|
},
|
|
@@ -381,7 +414,7 @@
|
|
|
381
414
|
"numbering": [
|
|
382
415
|
1,
|
|
383
416
|
2,
|
|
384
|
-
|
|
417
|
+
11
|
|
385
418
|
],
|
|
386
419
|
"parent": "root"
|
|
387
420
|
},
|