@commercetools/nimbus-mcp 3.0.0 → 3.1.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.
Files changed (180) hide show
  1. package/data/docs/route-manifest.json +1407 -794
  2. package/data/docs/routes/components-data-display-data-table.json +66 -34
  3. package/data/docs/routes/components-data-display-draggable-list.json +62 -7
  4. package/data/docs/routes/components-inputs-checkbox.json +2 -2
  5. package/data/docs/routes/components-layout-splitter.json +654 -0
  6. package/data/docs/routes/components-utilities-region.json +265 -0
  7. package/data/docs/routes/home-getting-started-bundler-plugins.json +248 -0
  8. package/data/docs/routes/hooks-usedraganddrop.json +310 -0
  9. package/data/docs/routes/patterns-actions.json +78 -0
  10. package/data/docs/routes/patterns-dialogs-confirmation-dialog.json +391 -0
  11. package/data/docs/routes/patterns-dialogs-form-dialog.json +358 -0
  12. package/data/docs/routes/patterns-dialogs.json +2 -2
  13. package/data/docs/routes/patterns-pages-public-page-layout.json +371 -0
  14. package/data/docs/routes/patterns-pages.json +78 -0
  15. package/data/docs/search-index.json +1 -1
  16. package/data/docs/types/AccordionContent.json +32 -32
  17. package/data/docs/types/AccordionHeader.json +102 -102
  18. package/data/docs/types/AccordionItem.json +28 -28
  19. package/data/docs/types/AccordionRoot.json +15 -15
  20. package/data/docs/types/AlertDescription.json +8 -8
  21. package/data/docs/types/AlertDismissButton.json +89 -89
  22. package/data/docs/types/AlertTitle.json +8 -8
  23. package/data/docs/types/Avatar.json +2 -2
  24. package/data/docs/types/Badge.json +2 -2
  25. package/data/docs/types/Body.json +6 -6
  26. package/data/docs/types/Box.json +6 -6
  27. package/data/docs/types/Button.json +97 -97
  28. package/data/docs/types/Calendar.json +111 -65
  29. package/data/docs/types/Caption.json +6 -6
  30. package/data/docs/types/CardRoot.json +2 -2
  31. package/data/docs/types/Cell.json +20 -20
  32. package/data/docs/types/Checkbox.json +99 -99
  33. package/data/docs/types/Code.json +10 -10
  34. package/data/docs/types/CollapsibleMotionContent.json +2 -2
  35. package/data/docs/types/CollapsibleMotionRoot.json +2 -2
  36. package/data/docs/types/CollapsibleMotionTrigger.json +4 -4
  37. package/data/docs/types/Column.json +8 -8
  38. package/data/docs/types/ColumnGroup.json +8 -8
  39. package/data/docs/types/ColumnHeader.json +18 -18
  40. package/data/docs/types/ComboBoxListBox.json +80 -80
  41. package/data/docs/types/ComboBoxOption.json +77 -77
  42. package/data/docs/types/ComboBoxPopover.json +77 -77
  43. package/data/docs/types/ComboBoxRoot.json +8 -8
  44. package/data/docs/types/ComboBoxSection.json +29 -29
  45. package/data/docs/types/ComboBoxTrigger.json +6 -6
  46. package/data/docs/types/ConfirmationDialog.json +224 -0
  47. package/data/docs/types/Content.json +2 -2
  48. package/data/docs/types/DataTable.json +17 -2
  49. package/data/docs/types/DataTableBody.json +24 -24
  50. package/data/docs/types/DataTableHeader.json +31 -31
  51. package/data/docs/types/DataTableRoot.json +17 -2
  52. package/data/docs/types/DataTableTable.json +35 -20
  53. package/data/docs/types/DateInput.json +84 -84
  54. package/data/docs/types/DatePicker.json +65 -65
  55. package/data/docs/types/DateRangePicker.json +99 -99
  56. package/data/docs/types/DateRangePickerField.json +99 -99
  57. package/data/docs/types/DefaultPageBackLink.json +16 -16
  58. package/data/docs/types/DefaultPageRoot.json +2 -2
  59. package/data/docs/types/DialogCloseTrigger.json +87 -87
  60. package/data/docs/types/DialogTrigger.json +2 -2
  61. package/data/docs/types/DragAndDropItemData.json +9 -0
  62. package/data/docs/types/DragAndDropProps.json +9 -0
  63. package/data/docs/types/DraggableListField.json +159 -70
  64. package/data/docs/types/DraggableListItem.json +63 -63
  65. package/data/docs/types/DraggableListRoot.json +159 -70
  66. package/data/docs/types/DrawerCloseTrigger.json +87 -87
  67. package/data/docs/types/DrawerTrigger.json +2 -2
  68. package/data/docs/types/FieldErrors.json +2 -2
  69. package/data/docs/types/Flex.json +22 -22
  70. package/data/docs/types/Footer.json +6 -6
  71. package/data/docs/types/FormDialog.json +198 -0
  72. package/data/docs/types/FormFieldRoot.json +2 -2
  73. package/data/docs/types/Grid.json +24 -24
  74. package/data/docs/types/Group.json +12 -12
  75. package/data/docs/types/Header.json +6 -6
  76. package/data/docs/types/Heading.json +8 -8
  77. package/data/docs/types/Icon.json +4 -4
  78. package/data/docs/types/IconButton.json +97 -97
  79. package/data/docs/types/IconToggleButton.json +84 -84
  80. package/data/docs/types/Image.json +30 -30
  81. package/data/docs/types/Indicator.json +6 -6
  82. package/data/docs/types/InlineSvg.json +2 -2
  83. package/data/docs/types/Item.json +6 -6
  84. package/data/docs/types/Kbd.json +8 -8
  85. package/data/docs/types/Link.json +31 -31
  86. package/data/docs/types/ListIndicator.json +6 -6
  87. package/data/docs/types/ListItem.json +6 -6
  88. package/data/docs/types/ListRoot.json +10 -10
  89. package/data/docs/types/LoadingSpinner.json +2 -2
  90. package/data/docs/types/MakeElementFocusable.json +19 -19
  91. package/data/docs/types/MenuItem.json +75 -75
  92. package/data/docs/types/MenuRoot.json +63 -63
  93. package/data/docs/types/MenuSection.json +35 -54
  94. package/data/docs/types/MenuSubmenuTrigger.json +5 -5
  95. package/data/docs/types/MenuTrigger.json +102 -102
  96. package/data/docs/types/MultilineTextInput.json +134 -134
  97. package/data/docs/types/MultilineTextInputField.json +131 -131
  98. package/data/docs/types/NumberInput.json +100 -100
  99. package/data/docs/types/NumberInputField.json +95 -95
  100. package/data/docs/types/PageContentColumn.json +6 -6
  101. package/data/docs/types/PageContentRoot.json +6 -6
  102. package/data/docs/types/PasswordInput.json +129 -129
  103. package/data/docs/types/PasswordInputField.json +129 -129
  104. package/data/docs/types/ProgressBar.json +14 -14
  105. package/data/docs/types/PublicPageLayout.json +99 -0
  106. package/data/docs/types/RadioInputOption.json +64 -64
  107. package/data/docs/types/RadioInputRoot.json +55 -55
  108. package/data/docs/types/RangeCalendar.json +90 -71
  109. package/data/docs/types/Region.json +114 -0
  110. package/data/docs/types/RegionProvider.json +25 -0
  111. package/data/docs/types/RegionTarget.json +112 -0
  112. package/data/docs/types/RichTextInput.json +2 -2
  113. package/data/docs/types/Root.json +10 -10
  114. package/data/docs/types/Row.json +6 -6
  115. package/data/docs/types/SPLITTER_SIZE_TOKENS.json +9 -0
  116. package/data/docs/types/ScrollArea.json +2 -2
  117. package/data/docs/types/SearchInput.json +136 -136
  118. package/data/docs/types/SearchInputField.json +131 -131
  119. package/data/docs/types/SelectOption.json +66 -66
  120. package/data/docs/types/SelectOptionGroup.json +22 -22
  121. package/data/docs/types/SelectOptions.json +74 -74
  122. package/data/docs/types/SelectRoot.json +102 -102
  123. package/data/docs/types/Separator.json +4 -4
  124. package/data/docs/types/SimpleGrid.json +28 -28
  125. package/data/docs/types/SplitButton.json +12 -12
  126. package/data/docs/types/Splitter.json +12 -0
  127. package/data/docs/types/SplitterAside.json +27 -0
  128. package/data/docs/types/SplitterHandle.json +27 -0
  129. package/data/docs/types/SplitterMain.json +27 -0
  130. package/data/docs/types/SplitterRoot.json +271 -0
  131. package/data/docs/types/SplitterSizeToken.json +9 -0
  132. package/data/docs/types/Stack.json +2 -2
  133. package/data/docs/types/StepsNextTrigger.json +2 -2
  134. package/data/docs/types/StepsPrevTrigger.json +2 -2
  135. package/data/docs/types/StepsRoot.json +2 -2
  136. package/data/docs/types/StepsTrigger.json +2 -2
  137. package/data/docs/types/Switch.json +38 -38
  138. package/data/docs/types/TabNavItem.json +18 -18
  139. package/data/docs/types/TabNavRoot.json +2 -2
  140. package/data/docs/types/TableBody.json +6 -6
  141. package/data/docs/types/TableCaption.json +6 -6
  142. package/data/docs/types/TableCell.json +20 -20
  143. package/data/docs/types/TableColumn.json +8 -8
  144. package/data/docs/types/TableColumnGroup.json +8 -8
  145. package/data/docs/types/TableColumnHeader.json +18 -18
  146. package/data/docs/types/TableFooter.json +6 -6
  147. package/data/docs/types/TableHeader.json +6 -6
  148. package/data/docs/types/TableRoot.json +32 -32
  149. package/data/docs/types/TableRow.json +6 -6
  150. package/data/docs/types/TableScrollArea.json +6 -6
  151. package/data/docs/types/TabsTab.json +2 -2
  152. package/data/docs/types/TagGroupRoot.json +27 -27
  153. package/data/docs/types/TagGroupTag.json +68 -68
  154. package/data/docs/types/TagGroupTagList.json +18 -18
  155. package/data/docs/types/Text.json +8 -8
  156. package/data/docs/types/TextInput.json +132 -132
  157. package/data/docs/types/TextInputField.json +129 -129
  158. package/data/docs/types/TimeInput.json +78 -78
  159. package/data/docs/types/ToggleButton.json +86 -86
  160. package/data/docs/types/ToggleButtonGroupButton.json +33 -33
  161. package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
  162. package/data/docs/types/Toolbar.json +12 -12
  163. package/data/docs/types/TooltipContent.json +31 -31
  164. package/data/docs/types/TooltipRoot.json +18 -18
  165. package/data/docs/types/Trigger.json +4 -4
  166. package/data/docs/types/UseDragAndDropOptions.json +9 -0
  167. package/data/docs/types/VisuallyHidden.json +7 -7
  168. package/data/docs/types/createArrayHandlers.json +12 -0
  169. package/data/docs/types/createItemsFromCsvDrop.json +833 -0
  170. package/data/docs/types/createItemsFromDirectoryDrop.json +833 -0
  171. package/data/docs/types/createItemsFromFileDrop.json +833 -0
  172. package/data/docs/types/createItemsFromImageDrop.json +833 -0
  173. package/data/docs/types/createItemsFromJsonDrop.json +833 -0
  174. package/data/docs/types/createItemsFromTextDrop.json +12 -0
  175. package/data/docs/types/createListDataHandlers.json +102 -0
  176. package/data/docs/types/manifest.json +29 -2
  177. package/data/docs/types/useDragAndDrop.json +174 -0
  178. package/data/docs/types/useRegion.json +1052 -0
  179. package/data/docs/types/useResponsiveSplitterSizes.json +143 -0
  180. package/package.json +6 -6
@@ -0,0 +1,654 @@
1
+ {
2
+ "meta": {
3
+ "id": "Components-Splitter",
4
+ "title": "Splitter",
5
+ "exportName": "Splitter",
6
+ "description": "A two-pane compound primitive for user-resizable layouts with a draggable, keyboard-operable handle.",
7
+ "lifecycleState": "Beta",
8
+ "order": 999,
9
+ "repoPath": "packages/nimbus/src/components/splitter/splitter.mdx",
10
+ "menu": [
11
+ "Components",
12
+ "Layout",
13
+ "Splitter"
14
+ ],
15
+ "route": "components/layout/splitter",
16
+ "tags": [
17
+ "component",
18
+ "layout",
19
+ "resizable"
20
+ ],
21
+ "toc": [
22
+ {
23
+ "value": "Overview",
24
+ "href": "#overview",
25
+ "depth": 2,
26
+ "numbering": [
27
+ 1,
28
+ 1
29
+ ],
30
+ "parent": "root"
31
+ },
32
+ {
33
+ "value": "Resources",
34
+ "href": "#resources",
35
+ "depth": 3,
36
+ "numbering": [
37
+ 1,
38
+ 1,
39
+ 1
40
+ ],
41
+ "parent": "root"
42
+ },
43
+ {
44
+ "value": "Variables",
45
+ "href": "#variables",
46
+ "depth": 2,
47
+ "numbering": [
48
+ 1,
49
+ 2
50
+ ],
51
+ "parent": "root"
52
+ },
53
+ {
54
+ "value": "Orientation",
55
+ "href": "#orientation",
56
+ "depth": 3,
57
+ "numbering": [
58
+ 1,
59
+ 2,
60
+ 1
61
+ ],
62
+ "parent": "root"
63
+ },
64
+ {
65
+ "value": "Collapse",
66
+ "href": "#collapse",
67
+ "depth": 3,
68
+ "numbering": [
69
+ 1,
70
+ 2,
71
+ 2
72
+ ],
73
+ "parent": "root"
74
+ },
75
+ {
76
+ "value": "Guidelines",
77
+ "href": "#guidelines",
78
+ "depth": 2,
79
+ "numbering": [
80
+ 1,
81
+ 3
82
+ ],
83
+ "parent": "root"
84
+ },
85
+ {
86
+ "value": "Best practices",
87
+ "href": "#best-practices",
88
+ "depth": 3,
89
+ "numbering": [
90
+ 1,
91
+ 3,
92
+ 1
93
+ ],
94
+ "parent": "root"
95
+ },
96
+ {
97
+ "value": "When to use",
98
+ "href": "#when-to-use",
99
+ "depth": 3,
100
+ "numbering": [
101
+ 1,
102
+ 3,
103
+ 2
104
+ ],
105
+ "parent": "root"
106
+ },
107
+ {
108
+ "value": "When not to use",
109
+ "href": "#when-not-to-use",
110
+ "depth": 3,
111
+ "numbering": [
112
+ 1,
113
+ 3,
114
+ 3
115
+ ],
116
+ "parent": "root"
117
+ }
118
+ ],
119
+ "figmaLink": "https://www.figma.com/design/gHbAJGfcrCv7f2bgzUQgHq/NIMBUS-Guidelines?node-id=1793-8790&m=dev",
120
+ "layout": "app-frame",
121
+ "tabs": [
122
+ {
123
+ "key": "overview",
124
+ "title": "Overview",
125
+ "order": 0
126
+ },
127
+ {
128
+ "key": "dev",
129
+ "title": "Implementation",
130
+ "order": 3
131
+ },
132
+ {
133
+ "key": "a11y",
134
+ "title": "Accessibility",
135
+ "order": 4
136
+ }
137
+ ]
138
+ },
139
+ "mdx": "\n## Overview\n\nThe Splitter lets people resize two adjacent regions by dragging the handle\nbetween them — a sidebar next to a content area, a list next to a detail view,\nan editor next to a preview. It is intentionally a **two-pane** primitive: the\nboundary is a single position, which keeps the interaction predictable and the\naccessibility model clean. Layouts with three or more regions are composed by\n**nesting** one Splitter inside another's pane.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n- [Figma library](https://www.figma.com/design/gHbAJGfcrCv7f2bgzUQgHq/NIMBUS-Guidelines?node-id=1793-8790&m=dev)\n- [W3C ARIA window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)\n\n## Variables\n\nGet familiar with the visual options.\n\n### Orientation\n\n`horizontal` (default) places the panes side by side with a vertical handle;\n`vertical` stacks them with a horizontal handle. The handle is keyboard operable\nin both orientations.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"400\">\n <Box height=\"240px\" width=\"320px\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root orientation=\"horizontal\" defaultSize={40}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Left</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Right</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n <Box height=\"240px\" width=\"320px\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root orientation=\"vertical\" defaultSize={40}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"teal.3\" h=\"100%\">\n <Text>Top</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\">\n <Text>Bottom</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n);\n```\n\n### Collapse\n\nThe aside can be made collapsible so a region — usually a sidebar or panel — can\nbe hidden and brought back. Collapsing animates the boundary to the aside's\ncollapsed size: fully hidden by default, or a thin rail when a collapsed size is\nset. The freed space goes to the main pane. Surface a visible control (a button)\nto toggle it; on the handle, Enter toggles collapse and double-click restores\nthe default split.\n\n```jsx live\nconst App = () => {\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <Button alignSelf=\"start\" onPress={() => setCollapsed((c) => !c)}>\n {collapsed ? \"Show nav\" : \"Hide nav\"}\n </Button>\n <Box height=\"240px\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={30}\n minSize={12}\n maxSize={80}\n collapsible\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Nav</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n );\n};\n```\n\n## Guidelines\n\n### Best practices\n\n- **Two panes per Splitter.** Nest splitters for layouts with three or more\n regions; each nested splitter is independently focusable and announced to\n assistive technology as its own widget.\n- **Bound the aside.** Set `minSize` and `maxSize` so dragging can't shrink the\n aside — or, via `maxSize`, the main pane — below its usable content width. Pair\n the bounds with content that scrolls inside the pane.\n- **Use collapsing for chrome.** A navigation or panel the user may want hidden\n should be the `collapsible` aside; surface a visible control (a button) for the\n mouse, since double-click is reserved for restoring the default split.\n- **Persist the layout** when the split is part of the user's workflow, so it\n survives across sessions.\n\n### When to use\n\n> [!TIP]\\\n> Use when\n\n- Side-by-side content the user benefits from resizing themselves.\n- App shells that pair a flexible navigation with a main content area.\n- Master/detail views where the relative split is part of the workflow.\n\n### When not to use\n\n> [!CAUTION]\\\n> When not to use\n\n- Static two-column layouts — use a flex or grid layout directly.\n- Narrow, mobile-first viewports — dragging is awkward; use a Drawer or stack\n the regions vertically.\n- More than two peer regions in one splitter — nest splitters instead.\n",
140
+ "views": {
141
+ "overview": {
142
+ "mdx": "\n## Overview\n\nThe Splitter lets people resize two adjacent regions by dragging the handle\nbetween them — a sidebar next to a content area, a list next to a detail view,\nan editor next to a preview. It is intentionally a **two-pane** primitive: the\nboundary is a single position, which keeps the interaction predictable and the\naccessibility model clean. Layouts with three or more regions are composed by\n**nesting** one Splitter inside another's pane.\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n- [Figma library](https://www.figma.com/design/gHbAJGfcrCv7f2bgzUQgHq/NIMBUS-Guidelines?node-id=1793-8790&m=dev)\n- [W3C ARIA window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)\n\n## Variables\n\nGet familiar with the visual options.\n\n### Orientation\n\n`horizontal` (default) places the panes side by side with a vertical handle;\n`vertical` stacks them with a horizontal handle. The handle is keyboard operable\nin both orientations.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"400\">\n <Box height=\"240px\" width=\"320px\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root orientation=\"horizontal\" defaultSize={40}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Left</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Right</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n <Box height=\"240px\" width=\"320px\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root orientation=\"vertical\" defaultSize={40}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"teal.3\" h=\"100%\">\n <Text>Top</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\">\n <Text>Bottom</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n);\n```\n\n### Collapse\n\nThe aside can be made collapsible so a region — usually a sidebar or panel — can\nbe hidden and brought back. Collapsing animates the boundary to the aside's\ncollapsed size: fully hidden by default, or a thin rail when a collapsed size is\nset. The freed space goes to the main pane. Surface a visible control (a button)\nto toggle it; on the handle, Enter toggles collapse and double-click restores\nthe default split.\n\n```jsx live\nconst App = () => {\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <Button alignSelf=\"start\" onPress={() => setCollapsed((c) => !c)}>\n {collapsed ? \"Show nav\" : \"Hide nav\"}\n </Button>\n <Box height=\"240px\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={30}\n minSize={12}\n maxSize={80}\n collapsible\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Nav</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n );\n};\n```\n\n## Guidelines\n\n### Best practices\n\n- **Two panes per Splitter.** Nest splitters for layouts with three or more\n regions; each nested splitter is independently focusable and announced to\n assistive technology as its own widget.\n- **Bound the aside.** Set `minSize` and `maxSize` so dragging can't shrink the\n aside — or, via `maxSize`, the main pane — below its usable content width. Pair\n the bounds with content that scrolls inside the pane.\n- **Use collapsing for chrome.** A navigation or panel the user may want hidden\n should be the `collapsible` aside; surface a visible control (a button) for the\n mouse, since double-click is reserved for restoring the default split.\n- **Persist the layout** when the split is part of the user's workflow, so it\n survives across sessions.\n\n### When to use\n\n> [!TIP]\\\n> Use when\n\n- Side-by-side content the user benefits from resizing themselves.\n- App shells that pair a flexible navigation with a main content area.\n- Master/detail views where the relative split is part of the workflow.\n\n### When not to use\n\n> [!CAUTION]\\\n> When not to use\n\n- Static two-column layouts — use a flex or grid layout directly.\n- Narrow, mobile-first viewports — dragging is awkward; use a Drawer or stack\n the regions vertically.\n- More than two peer regions in one splitter — nest splitters instead.\n",
143
+ "toc": [
144
+ {
145
+ "value": "Overview",
146
+ "href": "#overview",
147
+ "depth": 2,
148
+ "numbering": [
149
+ 1,
150
+ 1
151
+ ],
152
+ "parent": "root"
153
+ },
154
+ {
155
+ "value": "Resources",
156
+ "href": "#resources",
157
+ "depth": 3,
158
+ "numbering": [
159
+ 1,
160
+ 1,
161
+ 1
162
+ ],
163
+ "parent": "root"
164
+ },
165
+ {
166
+ "value": "Variables",
167
+ "href": "#variables",
168
+ "depth": 2,
169
+ "numbering": [
170
+ 1,
171
+ 2
172
+ ],
173
+ "parent": "root"
174
+ },
175
+ {
176
+ "value": "Orientation",
177
+ "href": "#orientation",
178
+ "depth": 3,
179
+ "numbering": [
180
+ 1,
181
+ 2,
182
+ 1
183
+ ],
184
+ "parent": "root"
185
+ },
186
+ {
187
+ "value": "Collapse",
188
+ "href": "#collapse",
189
+ "depth": 3,
190
+ "numbering": [
191
+ 1,
192
+ 2,
193
+ 2
194
+ ],
195
+ "parent": "root"
196
+ },
197
+ {
198
+ "value": "Guidelines",
199
+ "href": "#guidelines",
200
+ "depth": 2,
201
+ "numbering": [
202
+ 1,
203
+ 3
204
+ ],
205
+ "parent": "root"
206
+ },
207
+ {
208
+ "value": "Best practices",
209
+ "href": "#best-practices",
210
+ "depth": 3,
211
+ "numbering": [
212
+ 1,
213
+ 3,
214
+ 1
215
+ ],
216
+ "parent": "root"
217
+ },
218
+ {
219
+ "value": "When to use",
220
+ "href": "#when-to-use",
221
+ "depth": 3,
222
+ "numbering": [
223
+ 1,
224
+ 3,
225
+ 2
226
+ ],
227
+ "parent": "root"
228
+ },
229
+ {
230
+ "value": "When not to use",
231
+ "href": "#when-not-to-use",
232
+ "depth": 3,
233
+ "numbering": [
234
+ 1,
235
+ 3,
236
+ 3
237
+ ],
238
+ "parent": "root"
239
+ }
240
+ ]
241
+ },
242
+ "a11y": {
243
+ "mdx": "\n## Accessibility\n\nAccessibility ensures that digital content and functionality are usable by\neveryone, including people with disabilities, by addressing visual, auditory,\ncognitive, and physical limitations.\n\nThe Splitter implements the\n[W3C window splitter](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)\npattern on top of React Aria's `useSeparator`, `useMove`, and `useFocusRing`.\nThe handle is a `role=\"separator\"` whose value reports the leading pane's size,\nso screen-reader users can perceive and adjust the boundary with the keyboard.\n\n```jsx live\nconst App = () => (\n <Box height=\"240px\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={30} minSize={10} maxSize={80}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Tab to the handle, then use the arrow keys</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Accessibility standards\n\n- The handle is keyboard-reachable in DOM order and operable with the arrow\n keys, Home/End, and Enter; it is removed from the tab order only when the\n splitter is `isDisabled`. While the aside is collapsed, the resize keys\n (arrows, Home/End) are inactive — Enter still toggles collapse — because a\n collapsed aside sits below its `minSize` and has no valid in-range position to\n resize to.\n- The handle exposes `role=\"separator\"` with `aria-orientation`, `aria-valuenow`\n (rounded for assistive technology), `aria-valuemin`, `aria-valuemax`,\n `aria-valuetext` (the size as a percentage), and `aria-controls` referencing\n the leading pane.\n- Provide a meaningful label: the handle ships a localized default (\"Resize\n panes\"); override it per handle with `aria-label` or `aria-labelledby` when a\n more specific label helps.\n- A visible focus ring appears on keyboard focus only (via `_focusVisible`),\n with contrast meeting WCAG 2.1 AA.\n- The interactive hit area is at least 24×24 CSS pixels (WCAG 2.5.5), even when\n the visible handle track is thinner.\n- Resizing has a keyboard alternative to dragging (arrow keys, Home/End), so the\n component does not rely on a dragging movement (WCAG 2.5.7).\n- Nested splitters are each announced as a self-contained separator widget with\n their own `aria-controls`, so assistive-technology users reason about each\n boundary locally.\n\n### Resources\n\n- [W3C ARIA Authoring Practices Guide (APG) - Window Splitter Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)\n- [React Aria useSeparator](https://react-spectrum.adobe.com/react-aria/useSeparator.html)\n- [React Aria useMove](https://react-spectrum.adobe.com/react-aria/useMove.html)\n",
244
+ "toc": [
245
+ {
246
+ "value": "Accessibility",
247
+ "href": "#accessibility",
248
+ "depth": 2,
249
+ "numbering": [
250
+ 1,
251
+ 1
252
+ ],
253
+ "parent": "root"
254
+ },
255
+ {
256
+ "value": "Accessibility standards",
257
+ "href": "#accessibility-standards",
258
+ "depth": 3,
259
+ "numbering": [
260
+ 1,
261
+ 1,
262
+ 1
263
+ ],
264
+ "parent": "root"
265
+ },
266
+ {
267
+ "value": "Resources",
268
+ "href": "#resources",
269
+ "depth": 3,
270
+ "numbering": [
271
+ 1,
272
+ 1,
273
+ 2
274
+ ],
275
+ "parent": "root"
276
+ }
277
+ ]
278
+ },
279
+ "dev": {
280
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Splitter, type SplitterRootProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\n`Splitter.Root` wraps one `Splitter.Aside` and one `Splitter.Main` with a\n`Splitter.Handle` between them. `Splitter.Aside` is the configurable pane you\nsize; `Splitter.Main` takes the remaining space. The initial split is set with\n`defaultSize` — a single percentage that always refers to the aside (the main\npane is `100 − size`).\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={30} minSize={15} maxSize={75}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>\n Drag the handle to resize. Tab to it, then use the arrow keys.\n </Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n## Usage examples\n\n### Orientation\n\n`orientation=\"vertical\"` stacks the panes and makes ArrowUp / ArrowDown the\nactive keys. The handle's `aria-orientation` reflects this (W3C separator\nsemantics describe the boundary axis, not the layout axis).\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"sm\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root orientation=\"vertical\" defaultSize={40}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"teal.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Aside constraints\n\n`minSize` and `maxSize` bound the single aside dimension. `minSize` (default\n`0`) is the aside's floor; `maxSize` (default `100`) caps how far the aside can\ngrow — which in turn fixes the main pane's floor at `100 − maxSize`. Because\nthere is one boundary, this single `[minSize, maxSize]` window on the aside\nfully describes both sides; there is no main-specific prop.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={30} minSize={15} maxSize={75}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside (min 15, max 75)</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main (floor 25)</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Collapsible aside\n\nSet `collapsible` to let the aside collapse, and control its collapsed state\nwith the boolean `collapsed`. Because collapse is plain controlled state, any\ncontrol can drive it; Enter on the focused handle toggles it too.\n`onCollapsedChange` fires on every transition. Only the aside collapses.\n\nWhile the aside is collapsed the handle can't be resized (drag and the arrow\nkeys are inactive) — a collapsed aside sits below its `minSize`, so a resize\ncould only snap. Reopen it with the control, Enter, or a double-click; the\ncollapsed state holds until you explicitly leave it.\n\n```jsx live-dev\nconst App = () => {\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <Button alignSelf=\"start\" onPress={() => setCollapsed((c) => !c)}>\n {collapsed ? \"Show aside\" : \"Hide aside\"}\n </Button>\n <Box height=\"2xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={30}\n minSize={10}\n maxSize={80}\n collapsible\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n );\n};\n```\n\n### Collapsing to a rail\n\nCollapse is a two-tier model. `minSize` is the floor for _dragging_ and the\narrow keys; `collapsedSize` is a distinct discrete state that collapse snaps to,\nbelow `minSize`. It defaults to `0` (the aside hides entirely) — set it to leave\na thin rail behind, e.g. an icon strip the user can click to expand.\n\n```jsx live-dev\nconst App = () => {\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <Button alignSelf=\"start\" onPress={() => setCollapsed((c) => !c)}>\n {collapsed ? \"Expand aside\" : \"Collapse aside to a rail\"}\n </Button>\n <Box height=\"2xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={30}\n minSize={15}\n maxSize={80}\n collapsible\n collapsedSize={6}\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n );\n};\n```\n\n### Uncontrolled collapse\n\nWhen you don't need to drive collapse from outside the splitter, seed the\ninitial collapsed state with `defaultCollapsed` and let the splitter own it.\nEnter on the handle toggles it from there, and double-click restores the default\nsplit.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={30}\n minSize={10}\n maxSize={80}\n collapsible\n defaultCollapsed\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside (starts collapsed)</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Tab to the handle and press Enter to toggle</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Keyboard step\n\nArrow keys move the boundary by `keyboardStep` percentage points (default `5`);\nHome / End jump it to the bounds. Accepts floats for finer control.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={30} keyboardStep={10}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>\n Tab to the handle — each arrow press moves the boundary 10%\n </Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Restoring the default split\n\nDouble-click the handle to restore the boundary to the sizes resolved on mount.\nSet `isDoubleClickDisabled` to turn that off; drag and keyboard stay active.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={50}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Drag, then double-click the handle to restore</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\" />\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={50} isDoubleClickDisabled>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"teal.3\" h=\"100%\">\n <Text>Double-click restore disabled</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\" />\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n);\n```\n\n### Disabled state\n\n`isDisabled` makes the whole splitter non-interactive: the handle leaves the tab\norder, gets `aria-disabled`, and ignores drag, keyboard, and collapse input.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root isDisabled defaultSize={30}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Persistence\n\nSize is uncontrolled by default, so persist it in your app with any storage:\nhydrate `defaultSize` from stored state and write the settled value back in\n`onSizeChangeEnd` (it fires once per settled interaction — drag end, each\nkeypress, collapse/expand, double-click restore — so no debouncing is needed). A\nsingle `number` round-trips: the value `onSizeChangeEnd` emits is exactly the\nshape `defaultSize` accepts. Collapse persists through its controlled boolean\nstate.\n\n```tsx\n// `useLocalStorage` is any storage hook of your choosing.\nconst [size, setSize] = useLocalStorage(\"ide-layout\", 30);\nconst [collapsed, setCollapsed] = useLocalStorage(\"ide-collapsed\", false);\n\n<Splitter.Root\n defaultSize={size}\n onSizeChangeEnd={setSize}\n minSize={10}\n maxSize={80}\n collapsible\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n>\n <Splitter.Aside>…</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>…</Splitter.Main>\n</Splitter.Root>;\n```\n\n`onSizeChange` (live, every drag tick ~60Hz) is also available when you need a\nreal-time read-out; prefer `onSizeChangeEnd` for persistence.\n\n### Controlled size\n\nPass `size` instead of `defaultSize` to drive the layout from outside — useful\nfor responsive layouts that swap the proportion per breakpoint, since external\nchanges apply in place (no remount, so pane content keeps its scroll and focus).\nControl is settled, not live: drag and keyboard stay smooth from internal state\nand report once via `onSizeChangeEnd`. Wire that callback and feed the value\nback, or the splitter keeps the last interactive size and behaves as\nuncontrolled from then on.\n\n```tsx\nconst [size, setSize] = useState(30);\n\n<Splitter.Root size={size} onSizeChangeEnd={setSize}>\n <Splitter.Aside>…</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>…</Splitter.Main>\n</Splitter.Root>;\n```\n\n### Pixel-precise sizes\n\nThe aside percentage carries full float precision end-to-end (nothing in the\nsize pipeline is rounded — only the handle's `aria-valuenow` is rounded, for\nassistive technology). To land on an exact pixel at a known container width,\npass the computed float — `250px` in an `800px` container is `31.25`.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"3xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={31.25}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>31.25%</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>68.75%</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n### Responsive pixel & token sizes with `useResponsiveSplitterSizes`\n\n`Splitter.Root` is percentage-native — it has no pixel code path. When you'd\nrather think in pixels (a fixed-width sidebar, an icon rail) or size per device,\nreach for the companion hook `useResponsiveSplitterSizes`. It is a **pixel/token\n→ percentage translator**: it measures the splitter's container, converts your\nconfig to the percentage the component wants, drives the controlled `size`\nchannel, and persists the result — all without the component gaining a pixel\npath.\n\nSpread its `rootProps` onto `Splitter.Root` and attach the `ref` it returns (the\n`ref` is required — the hook measures the container through it):\n\n```tsx\nconst { rootProps } = useResponsiveSplitterSizes({\n orientation: \"horizontal\",\n persistKey: \"app:main-splitter\",\n size: \"xs\", // 320px — size tokens resolve to pixels\n minSize: \"3xs\", // 224px\n maxSize: \"lg\", // 512px\n});\n\n<Splitter.Root {...rootProps} collapsible>\n <Splitter.Aside>…</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>…</Splitter.Main>\n</Splitter.Root>;\n```\n\n**Units.** A value is a `number` (pixels), a size token (`3xs`–`8xl` or\n`breakpoint-sm`…`breakpoint-2xl`, resolving to pixels), or a `\"N%\"` string\n(passed through untranslated). Note the contrast with `Splitter.Root`'s own\n`size` / `minSize` / … props, which are **percentages**: through the hook, a\nbare number means pixels. Because the hook owns the full facade (`size` plus\n`minSize` / `maxSize` / `collapsedSize`), you don't hand-write percentages on\nthe root when you use it.\n\n**Responsive by container width.** Any dimension can be a map keyed by container\n**min-width thresholds** (pixels or tokens) — a min-width cascade resolved\nagainst the splitter's own width, not the viewport. The largest threshold `≤`\nthe measured width wins; the smallest entry also applies below it.\n\n```tsx\nconst { rootProps } = useResponsiveSplitterSizes({\n size: { 0: \"xs\", \"breakpoint-md\": \"30%\" }, // \"xs\" (320px) below 768px, 30% above\n});\n```\n\n**Persistence.** Pass a `persistKey` (and optionally a `storage` adapter,\ndefaulting to `localStorage`) and the hook stores the settled size **in pixels**\nper band, so a dragged `320px` re-pins to `320px` across reloads and container\nresizes. `collapsedSize` is static config and is never persisted; the latest\nexpanded size survives collapse and expand.\n\n### Three or more regions\n\nThe Splitter is two-pane by design. For additional regions, nest a Splitter\ninside a pane's children; each splitter owns its own state independently. The\ninner aside can sit on either side of its main pane — here it trails as a right\npanel.\n\n```jsx live-dev\nconst App = () => (\n <Box height=\"sm\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root defaultSize={25}>\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside (outer)</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Splitter.Root defaultSize={35}>\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n <Splitter.Handle />\n <Splitter.Aside>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\">\n <Text>Aside (inner)</Text>\n </Box>\n </Splitter.Aside>\n </Splitter.Root>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n);\n```\n\n## Common patterns\n\n### IDE-style layout\n\nThe most common real-world composition: a persisted split (`defaultSize` +\n`onSizeChangeEnd`) with a `collapsible` aside that snaps to a rail\n(`collapsedSize`), driven by both a toolbar button and the handle, plus aside\nbounds and a specific handle label.\n\n```jsx live-dev\nconst App = () => {\n const [size, setSize] = useState(22);\n const [collapsed, setCollapsed] = useState(false);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <Button alignSelf=\"start\" onPress={() => setCollapsed((c) => !c)}>\n {collapsed ? \"Show aside\" : \"Hide aside\"}\n </Button>\n <Box height=\"xs\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n defaultSize={size}\n onSizeChangeEnd={setSize}\n minSize={15}\n maxSize={70}\n collapsible\n collapsedSize={4}\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"indigo.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle aria-label=\"Resize aside\" />\n <Splitter.Main>\n <Box p=\"400\" bg=\"amber.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n </Stack>\n );\n};\n```\n\n### Vertical with persistence\n\nA vertical split with a persisted boundary and aside bounds so neither region\ncollapses to nothing while dragging.\n\n```jsx live-dev\nconst App = () => {\n const [size, setSize] = useState(60);\n\n return (\n <Box height=\"sm\" width=\"100%\" borderWidth=\"25\" borderColor=\"neutral.6\">\n <Splitter.Root\n orientation=\"vertical\"\n defaultSize={size}\n onSizeChangeEnd={setSize}\n minSize={25}\n maxSize={80}\n >\n <Splitter.Aside>\n <Box p=\"400\" bg=\"teal.3\" h=\"100%\">\n <Text>Aside</Text>\n </Box>\n </Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Box p=\"400\" bg=\"rose.3\" h=\"100%\">\n <Text>Main</Text>\n </Box>\n </Splitter.Main>\n </Splitter.Root>\n </Box>\n );\n};\n```\n\n## Component requirements\n\n### Structure\n\n- `Splitter.Root` must contain exactly one `Splitter.Aside` and one\n `Splitter.Main` with one `Splitter.Handle` between them; a development-time\n warning is emitted otherwise. The aside may be placed before or after the main\n pane (a leading or trailing panel) — `size` always refers to the aside.\n- All sizing and collapse configuration (`defaultSize` / `size`, `minSize`,\n `maxSize`, `collapsible`, `collapsedSize`) lives on `Splitter.Root`. Panes\n take only their content and an optional `id` for analytics/testing — nothing\n is configured on the pane itself.\n\n## Accessibility\n\nThe Splitter handles the W3C window-splitter semantics internally: the handle is\n`role=\"separator\"` with `aria-orientation`, `aria-valuenow` / `aria-valuemin` /\n`aria-valuemax`, `aria-valuetext`, and `aria-controls` pointing at the leading\npane. For the full conformance details, see the Accessibility tab.\n\n#### Labeling\n\nThe handle ships a localized default `aria-label` (\"Resize panes\"); override it\nper handle with `aria-label` or `aria-labelledby` for a more specific label:\n\n```tsx\n<Splitter.Handle aria-label={msg.format(\"resizeAside\")} />\n```\n\n#### Persistent ID\n\nIf your use case requires tracking and analytics, it is good practice to add a\n**persistent**, **unique** id to the handle:\n\n```tsx\nconst PERSISTENT_ID = \"ide-layout-splitter-handle\";\n\nexport const Example = () => (\n <Splitter.Root defaultSize={30}>\n <Splitter.Aside>…</Splitter.Aside>\n <Splitter.Handle id={PERSISTENT_ID} />\n <Splitter.Main>…</Splitter.Main>\n </Splitter.Root>\n);\n```\n\n#### Keyboard navigation\n\nWhen the handle has focus:\n\n- `Tab` / `Shift+Tab`: Move focus to/from the handle (in DOM order).\n- `ArrowLeft` / `ArrowRight` (horizontal) or `ArrowUp` / `ArrowDown` (vertical):\n Move the boundary by `keyboardStep` percentage points (inactive while the\n aside is collapsed).\n- `Home` / `End`: Jump the boundary to the aside's minimum / maximum (inactive\n while the aside is collapsed).\n- `Enter`: Toggle collapse of the aside (when `collapsible`).\n\n## API reference\n\n<PropsTable id=\"Splitter\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Splitter\nin your application. As the component's internal functionality is already tested\nby Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic Rendering\n\nMinimal Splitter — a configurable aside and a main pane —\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { useState } from \"react\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NimbusProvider, Splitter } from \"@commercetools/nimbus\";\n\ndescribe(\"Splitter - Basic rendering\", () => {\n it(\"renders an aside, a main pane, and one handle\", async () => {\n render(\n <NimbusProvider>\n <Splitter.Root defaultSize={30}>\n <Splitter.Aside>Aside</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>Main</Splitter.Main>\n </Splitter.Root>\n </NimbusProvider>\n );\n\n const handle = await screen.findByRole(\"separator\");\n expect(handle).toBeInTheDocument();\n });\n});\n```\n\n### Persistence with any storage\n\nHydrate `defaultSize` from stored state and persist the\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { useState } from \"react\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NimbusProvider, Splitter } from \"@commercetools/nimbus\";\n\ndescribe(\"Splitter - persistence\", () => {\n it(\"hydrates from the stored size on first render\", async () => {\n // Stand-in for a `useLocalStorage`-style hook seeded from storage.\n const Demo = () => {\n const [size, setSize] = useState(25);\n return (\n <Splitter.Root\n defaultSize={size}\n onSizeChangeEnd={setSize}\n minSize={5}\n maxSize={95}\n >\n <Splitter.Aside>Aside</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>Main</Splitter.Main>\n </Splitter.Root>\n );\n };\n\n render(\n <NimbusProvider>\n <Demo />\n </NimbusProvider>\n );\n\n const handle = await screen.findByRole(\"separator\");\n await waitFor(() => {\n expect(Number(handle.getAttribute(\"aria-valuenow\"))).toBe(25);\n });\n });\n});\n```\n\n### Controlled size from anywhere\n\nDrive the layout with the `size` prop and update it from\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { useState } from \"react\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NimbusProvider, Splitter } from \"@commercetools/nimbus\";\n\ndescribe(\"Splitter - controlled size\", () => {\n it(\"reflects an external size change in place\", async () => {\n const user = userEvent.setup();\n const Demo = () => {\n const [size, setSize] = useState(30);\n return (\n <>\n <button type=\"button\" onClick={() => setSize(60)}>\n widen-aside\n </button>\n <Splitter.Root\n size={size}\n onSizeChangeEnd={setSize}\n minSize={5}\n maxSize={95}\n >\n <Splitter.Aside>Aside</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>Main</Splitter.Main>\n </Splitter.Root>\n </>\n );\n };\n\n render(\n <NimbusProvider>\n <Demo />\n </NimbusProvider>\n );\n\n const handle = await screen.findByRole(\"separator\");\n await waitFor(() => {\n expect(Number(handle.getAttribute(\"aria-valuenow\"))).toBe(30);\n });\n\n await user.click(screen.getByText(\"widen-aside\"));\n await waitFor(() => {\n expect(Number(handle.getAttribute(\"aria-valuenow\"))).toBe(60);\n });\n });\n});\n```\n\n### Controlled collapse from anywhere\n\nCollapse is plain controlled boolean state, so a button\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { useState } from \"react\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NimbusProvider, Splitter } from \"@commercetools/nimbus\";\n\ndescribe(\"Splitter - controlled collapse\", () => {\n it(\"collapses the aside from a button outside the subtree\", async () => {\n const user = userEvent.setup();\n const Demo = () => {\n const [collapsed, setCollapsed] = useState(false);\n return (\n <>\n <button type=\"button\" onClick={() => setCollapsed((c) => !c)}>\n toggle-aside\n </button>\n <Splitter.Root\n defaultSize={30}\n minSize={5}\n maxSize={95}\n collapsible\n collapsed={collapsed}\n onCollapsedChange={setCollapsed}\n >\n <Splitter.Aside>Aside</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>Main</Splitter.Main>\n </Splitter.Root>\n </>\n );\n };\n\n render(\n <NimbusProvider>\n <Demo />\n </NimbusProvider>\n );\n\n await user.click(screen.getByText(\"toggle-aside\"));\n const handle = await screen.findByRole(\"separator\");\n await waitFor(() => {\n expect(Number(handle.getAttribute(\"aria-valuenow\"))).toBe(0);\n });\n });\n});\n```\n\n### Nested splitters for 3+ regions\n\nEach nested Splitter is an independent widget.\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { useState } from \"react\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { NimbusProvider, Splitter } from \"@commercetools/nimbus\";\n\ndescribe(\"Splitter - Nested\", () => {\n it(\"nests inside a pane to express three regions\", async () => {\n render(\n <NimbusProvider>\n <Splitter.Root defaultSize={25}>\n <Splitter.Aside>Aside</Splitter.Aside>\n <Splitter.Handle />\n <Splitter.Main>\n <Splitter.Root defaultSize={35}>\n <Splitter.Main>Main</Splitter.Main>\n <Splitter.Handle />\n <Splitter.Aside>Aside</Splitter.Aside>\n </Splitter.Root>\n </Splitter.Main>\n </Splitter.Root>\n </NimbusProvider>\n );\n\n const handles = await screen.findAllByRole(\"separator\");\n expect(handles).toHaveLength(2);\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-splitter--docs)\n- [React Aria useSeparator](https://react-spectrum.adobe.com/react-aria/useSeparator.html)\n- [W3C ARIA window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)\n",
281
+ "toc": [
282
+ {
283
+ "value": "Getting started",
284
+ "href": "#getting-started",
285
+ "depth": 2,
286
+ "numbering": [
287
+ 1,
288
+ 1
289
+ ],
290
+ "parent": "root"
291
+ },
292
+ {
293
+ "value": "Import",
294
+ "href": "#import",
295
+ "depth": 3,
296
+ "numbering": [
297
+ 1,
298
+ 1,
299
+ 1
300
+ ],
301
+ "parent": "root"
302
+ },
303
+ {
304
+ "value": "Basic usage",
305
+ "href": "#basic-usage",
306
+ "depth": 3,
307
+ "numbering": [
308
+ 1,
309
+ 1,
310
+ 2
311
+ ],
312
+ "parent": "root"
313
+ },
314
+ {
315
+ "value": "Usage examples",
316
+ "href": "#usage-examples",
317
+ "depth": 2,
318
+ "numbering": [
319
+ 1,
320
+ 2
321
+ ],
322
+ "parent": "root"
323
+ },
324
+ {
325
+ "value": "Orientation",
326
+ "href": "#orientation",
327
+ "depth": 3,
328
+ "numbering": [
329
+ 1,
330
+ 2,
331
+ 1
332
+ ],
333
+ "parent": "root"
334
+ },
335
+ {
336
+ "value": "Aside constraints",
337
+ "href": "#aside-constraints",
338
+ "depth": 3,
339
+ "numbering": [
340
+ 1,
341
+ 2,
342
+ 2
343
+ ],
344
+ "parent": "root"
345
+ },
346
+ {
347
+ "value": "Collapsible aside",
348
+ "href": "#collapsible-aside",
349
+ "depth": 3,
350
+ "numbering": [
351
+ 1,
352
+ 2,
353
+ 3
354
+ ],
355
+ "parent": "root"
356
+ },
357
+ {
358
+ "value": "Collapsing to a rail",
359
+ "href": "#collapsing-to-a-rail",
360
+ "depth": 3,
361
+ "numbering": [
362
+ 1,
363
+ 2,
364
+ 4
365
+ ],
366
+ "parent": "root"
367
+ },
368
+ {
369
+ "value": "Uncontrolled collapse",
370
+ "href": "#uncontrolled-collapse",
371
+ "depth": 3,
372
+ "numbering": [
373
+ 1,
374
+ 2,
375
+ 5
376
+ ],
377
+ "parent": "root"
378
+ },
379
+ {
380
+ "value": "Keyboard step",
381
+ "href": "#keyboard-step",
382
+ "depth": 3,
383
+ "numbering": [
384
+ 1,
385
+ 2,
386
+ 6
387
+ ],
388
+ "parent": "root"
389
+ },
390
+ {
391
+ "value": "Restoring the default split",
392
+ "href": "#restoring-the-default-split",
393
+ "depth": 3,
394
+ "numbering": [
395
+ 1,
396
+ 2,
397
+ 7
398
+ ],
399
+ "parent": "root"
400
+ },
401
+ {
402
+ "value": "Disabled state",
403
+ "href": "#disabled-state",
404
+ "depth": 3,
405
+ "numbering": [
406
+ 1,
407
+ 2,
408
+ 8
409
+ ],
410
+ "parent": "root"
411
+ },
412
+ {
413
+ "value": "Persistence",
414
+ "href": "#persistence",
415
+ "depth": 3,
416
+ "numbering": [
417
+ 1,
418
+ 2,
419
+ 9
420
+ ],
421
+ "parent": "root"
422
+ },
423
+ {
424
+ "value": "Controlled size",
425
+ "href": "#controlled-size",
426
+ "depth": 3,
427
+ "numbering": [
428
+ 1,
429
+ 2,
430
+ 10
431
+ ],
432
+ "parent": "root"
433
+ },
434
+ {
435
+ "value": "Pixel-precise sizes",
436
+ "href": "#pixel-precise-sizes",
437
+ "depth": 3,
438
+ "numbering": [
439
+ 1,
440
+ 2,
441
+ 11
442
+ ],
443
+ "parent": "root"
444
+ },
445
+ {
446
+ "value": "Responsive pixel & token sizes with useResponsiveSplitterSizes",
447
+ "href": "#responsive-pixel--token-sizes-with-useresponsivesplittersizes",
448
+ "depth": 3,
449
+ "numbering": [
450
+ 1,
451
+ 2,
452
+ 12
453
+ ],
454
+ "parent": "root"
455
+ },
456
+ {
457
+ "value": "Three or more regions",
458
+ "href": "#three-or-more-regions",
459
+ "depth": 3,
460
+ "numbering": [
461
+ 1,
462
+ 2,
463
+ 13
464
+ ],
465
+ "parent": "root"
466
+ },
467
+ {
468
+ "value": "Common patterns",
469
+ "href": "#common-patterns",
470
+ "depth": 2,
471
+ "numbering": [
472
+ 1,
473
+ 3
474
+ ],
475
+ "parent": "root"
476
+ },
477
+ {
478
+ "value": "IDE-style layout",
479
+ "href": "#ide-style-layout",
480
+ "depth": 3,
481
+ "numbering": [
482
+ 1,
483
+ 3,
484
+ 1
485
+ ],
486
+ "parent": "root"
487
+ },
488
+ {
489
+ "value": "Vertical with persistence",
490
+ "href": "#vertical-with-persistence",
491
+ "depth": 3,
492
+ "numbering": [
493
+ 1,
494
+ 3,
495
+ 2
496
+ ],
497
+ "parent": "root"
498
+ },
499
+ {
500
+ "value": "Component requirements",
501
+ "href": "#component-requirements",
502
+ "depth": 2,
503
+ "numbering": [
504
+ 1,
505
+ 4
506
+ ],
507
+ "parent": "root"
508
+ },
509
+ {
510
+ "value": "Structure",
511
+ "href": "#structure",
512
+ "depth": 3,
513
+ "numbering": [
514
+ 1,
515
+ 4,
516
+ 1
517
+ ],
518
+ "parent": "root"
519
+ },
520
+ {
521
+ "value": "Accessibility",
522
+ "href": "#accessibility",
523
+ "depth": 2,
524
+ "numbering": [
525
+ 1,
526
+ 5
527
+ ],
528
+ "parent": "root"
529
+ },
530
+ {
531
+ "value": "Labeling",
532
+ "href": "#labeling",
533
+ "depth": 4,
534
+ "numbering": [
535
+ 1,
536
+ 5,
537
+ 1,
538
+ 1
539
+ ],
540
+ "parent": "root"
541
+ },
542
+ {
543
+ "value": "Persistent ID",
544
+ "href": "#persistent-id",
545
+ "depth": 4,
546
+ "numbering": [
547
+ 1,
548
+ 5,
549
+ 1,
550
+ 2
551
+ ],
552
+ "parent": "root"
553
+ },
554
+ {
555
+ "value": "Keyboard navigation",
556
+ "href": "#keyboard-navigation",
557
+ "depth": 4,
558
+ "numbering": [
559
+ 1,
560
+ 5,
561
+ 1,
562
+ 3
563
+ ],
564
+ "parent": "root"
565
+ },
566
+ {
567
+ "value": "API reference",
568
+ "href": "#api-reference",
569
+ "depth": 2,
570
+ "numbering": [
571
+ 1,
572
+ 6
573
+ ],
574
+ "parent": "root"
575
+ },
576
+ {
577
+ "value": "Testing your implementation",
578
+ "href": "#testing-your-implementation",
579
+ "depth": 2,
580
+ "numbering": [
581
+ 1,
582
+ 7
583
+ ],
584
+ "parent": "root"
585
+ },
586
+ {
587
+ "value": "Basic Rendering",
588
+ "href": "#basic-rendering",
589
+ "depth": 3,
590
+ "numbering": [
591
+ 1,
592
+ 7,
593
+ 1
594
+ ],
595
+ "parent": "root"
596
+ },
597
+ {
598
+ "value": "Persistence with any storage",
599
+ "href": "#persistence-with-any-storage",
600
+ "depth": 3,
601
+ "numbering": [
602
+ 1,
603
+ 7,
604
+ 2
605
+ ],
606
+ "parent": "root"
607
+ },
608
+ {
609
+ "value": "Controlled size from anywhere",
610
+ "href": "#controlled-size-from-anywhere",
611
+ "depth": 3,
612
+ "numbering": [
613
+ 1,
614
+ 7,
615
+ 3
616
+ ],
617
+ "parent": "root"
618
+ },
619
+ {
620
+ "value": "Controlled collapse from anywhere",
621
+ "href": "#controlled-collapse-from-anywhere",
622
+ "depth": 3,
623
+ "numbering": [
624
+ 1,
625
+ 7,
626
+ 4
627
+ ],
628
+ "parent": "root"
629
+ },
630
+ {
631
+ "value": "Nested splitters for 3+ regions",
632
+ "href": "#nested-splitters-for-3-regions",
633
+ "depth": 3,
634
+ "numbering": [
635
+ 1,
636
+ 7,
637
+ 5
638
+ ],
639
+ "parent": "root"
640
+ },
641
+ {
642
+ "value": "Resources",
643
+ "href": "#resources",
644
+ "depth": 2,
645
+ "numbering": [
646
+ 1,
647
+ 8
648
+ ],
649
+ "parent": "root"
650
+ }
651
+ ]
652
+ }
653
+ }
654
+ }