@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.
Files changed (223) hide show
  1. package/README.md +9 -1
  2. package/data/docs/route-manifest.json +1608 -143
  3. package/data/docs/routes/components-buttons-button.json +25 -2
  4. package/data/docs/routes/components-chat-chat-message-list.json +448 -0
  5. package/data/docs/routes/components-chat-chat-message.json +580 -0
  6. package/data/docs/routes/components-content-markdown.json +548 -0
  7. package/data/docs/routes/components-data-display-item-group.json +413 -0
  8. package/data/docs/routes/components-data-display-item.json +611 -0
  9. package/data/docs/routes/components-feedback-activity-indicator.json +396 -0
  10. package/data/docs/routes/components-feedback-skeleton.json +481 -0
  11. package/data/docs/routes/components-feedback-skeletoncircle.json +429 -0
  12. package/data/docs/routes/components-feedback-skeletontext.json +417 -0
  13. package/data/docs/routes/components-feedback-tooltip.json +23 -1
  14. package/data/docs/routes/components-inputs-drop-zone.json +562 -0
  15. package/data/docs/routes/components-inputs-file-trigger.json +421 -0
  16. package/data/docs/routes/components-inputs-range-slider.json +633 -0
  17. package/data/docs/routes/components-inputs-slider.json +611 -0
  18. package/data/docs/routes/components-layout-splitter.json +3 -3
  19. package/data/docs/routes/components-media-avatar.json +16 -5
  20. package/data/docs/routes/components-navigation-breadcrumbs.json +565 -0
  21. package/data/docs/routes/components-navigation-menu.json +1 -1
  22. package/data/docs/routes/components-navigation-tabnav.json +80 -14
  23. package/data/docs/routes/components-navigation-tabs.json +44 -11
  24. package/data/docs/routes/components-navigation-tree.json +445 -0
  25. package/data/docs/routes/home-getting-started-mcp-server-overview.json +2 -2
  26. package/data/docs/routes/home-getting-started-mcp-server-setup.json +2 -2
  27. package/data/docs/routes/icons.json +3 -3
  28. package/data/docs/routes/patterns-buttons-floating-action-button.json +392 -0
  29. package/data/docs/routes/patterns-buttons.json +58 -0
  30. package/data/docs/search-index.json +1 -1
  31. package/data/docs/types/AccordionContent.json +24 -24
  32. package/data/docs/types/AccordionHeader.json +88 -88
  33. package/data/docs/types/AccordionItem.json +20 -20
  34. package/data/docs/types/AccordionRoot.json +14 -14
  35. package/data/docs/types/ActivityIndicator.json +202 -0
  36. package/data/docs/types/AlertDescription.json +8 -8
  37. package/data/docs/types/AlertDismissButton.json +98 -81
  38. package/data/docs/types/AlertTitle.json +8 -8
  39. package/data/docs/types/Avatar.json +19 -2
  40. package/data/docs/types/Badge.json +2 -2
  41. package/data/docs/types/Body.json +6 -6
  42. package/data/docs/types/Box.json +6 -6
  43. package/data/docs/types/Breadcrumbs.json +12 -0
  44. package/data/docs/types/BreadcrumbsItem.json +296 -0
  45. package/data/docs/types/BreadcrumbsItemProps.json +9 -0
  46. package/data/docs/types/BreadcrumbsProps.json +9 -0
  47. package/data/docs/types/BreadcrumbsRoot.json +110 -0
  48. package/data/docs/types/Button.json +106 -89
  49. package/data/docs/types/Calendar.json +56 -56
  50. package/data/docs/types/Caption.json +6 -6
  51. package/data/docs/types/CardRoot.json +2 -2
  52. package/data/docs/types/Cell.json +6 -6
  53. package/data/docs/types/ChatMessage.json +12 -0
  54. package/data/docs/types/ChatMessageActions.json +42 -0
  55. package/data/docs/types/ChatMessageAvatar.json +140 -0
  56. package/data/docs/types/ChatMessageBody.json +42 -0
  57. package/data/docs/types/ChatMessageList.json +12 -0
  58. package/data/docs/types/ChatMessageListItem.json +42 -0
  59. package/data/docs/types/ChatMessageListRoot.json +78 -0
  60. package/data/docs/types/ChatMessageMeta.json +42 -0
  61. package/data/docs/types/ChatMessageRoot.json +119 -0
  62. package/data/docs/types/ChatMessageTyping.json +42 -0
  63. package/data/docs/types/Checkbox.json +86 -86
  64. package/data/docs/types/Code.json +11 -11
  65. package/data/docs/types/Column.json +6 -6
  66. package/data/docs/types/ColumnGroup.json +6 -6
  67. package/data/docs/types/ColumnHeader.json +6 -6
  68. package/data/docs/types/ComboBoxListBox.json +70 -70
  69. package/data/docs/types/ComboBoxOption.json +64 -64
  70. package/data/docs/types/ComboBoxPopover.json +91 -70
  71. package/data/docs/types/ComboBoxRoot.json +8 -8
  72. package/data/docs/types/ComboBoxSection.json +24 -24
  73. package/data/docs/types/ComboBoxTrigger.json +6 -6
  74. package/data/docs/types/DataTable.json +2 -2
  75. package/data/docs/types/DataTableBody.json +20 -20
  76. package/data/docs/types/DataTableHeader.json +25 -25
  77. package/data/docs/types/DataTableRoot.json +2 -2
  78. package/data/docs/types/DataTableTable.json +6 -6
  79. package/data/docs/types/DateInput.json +73 -73
  80. package/data/docs/types/DatePicker.json +58 -58
  81. package/data/docs/types/DateRangePicker.json +86 -86
  82. package/data/docs/types/DateRangePickerField.json +86 -86
  83. package/data/docs/types/DefaultPageRoot.json +2 -2
  84. package/data/docs/types/DialogCloseTrigger.json +96 -79
  85. package/data/docs/types/DraggableListField.json +88 -60
  86. package/data/docs/types/DraggableListItem.json +52 -52
  87. package/data/docs/types/DraggableListRoot.json +88 -60
  88. package/data/docs/types/DrawerCloseTrigger.json +96 -79
  89. package/data/docs/types/DropZone.json +161 -0
  90. package/data/docs/types/FieldErrors.json +2 -2
  91. package/data/docs/types/FileTrigger.json +130 -0
  92. package/data/docs/types/Flex.json +22 -22
  93. package/data/docs/types/FloatingActionButton.json +1036 -0
  94. package/data/docs/types/Footer.json +6 -6
  95. package/data/docs/types/FormActionBar.json +1 -1
  96. package/data/docs/types/FormFieldRoot.json +2 -2
  97. package/data/docs/types/Grid.json +24 -24
  98. package/data/docs/types/Group.json +12 -12
  99. package/data/docs/types/Header.json +6 -6
  100. package/data/docs/types/Heading.json +8 -8
  101. package/data/docs/types/Icon.json +4 -4
  102. package/data/docs/types/IconButton.json +106 -89
  103. package/data/docs/types/IconToggleButton.json +71 -71
  104. package/data/docs/types/Image.json +43 -43
  105. package/data/docs/types/Indicator.json +6 -6
  106. package/data/docs/types/InlineSvg.json +2 -2
  107. package/data/docs/types/Item.json +6 -6
  108. package/data/docs/types/ItemActions.json +27 -0
  109. package/data/docs/types/ItemContent.json +27 -0
  110. package/data/docs/types/ItemDescription.json +27 -0
  111. package/data/docs/types/ItemFooter.json +27 -0
  112. package/data/docs/types/ItemGroup.json +12 -0
  113. package/data/docs/types/ItemGroupRoot.json +27 -0
  114. package/data/docs/types/ItemGroupSeparator.json +27 -0
  115. package/data/docs/types/ItemHeader.json +27 -0
  116. package/data/docs/types/ItemMedia.json +56 -0
  117. package/data/docs/types/ItemRoot.json +239 -0
  118. package/data/docs/types/ItemTitle.json +27 -0
  119. package/data/docs/types/Kbd.json +8 -8
  120. package/data/docs/types/Link.json +14 -14
  121. package/data/docs/types/ListIndicator.json +6 -6
  122. package/data/docs/types/ListItem.json +6 -6
  123. package/data/docs/types/ListRoot.json +11 -11
  124. package/data/docs/types/LoadingSpinner.json +2 -2
  125. package/data/docs/types/MakeElementFocusable.json +18 -18
  126. package/data/docs/types/Markdown.json +183 -0
  127. package/data/docs/types/MenuItem.json +62 -62
  128. package/data/docs/types/MenuRoot.json +55 -55
  129. package/data/docs/types/MenuSection.json +30 -30
  130. package/data/docs/types/MenuSubmenuTrigger.json +4 -4
  131. package/data/docs/types/MenuTrigger.json +88 -88
  132. package/data/docs/types/MultilineTextInput.json +104 -104
  133. package/data/docs/types/MultilineTextInputField.json +102 -102
  134. package/data/docs/types/NumberInput.json +86 -86
  135. package/data/docs/types/NumberInputField.json +82 -82
  136. package/data/docs/types/PageContentColumn.json +6 -6
  137. package/data/docs/types/PageContentRoot.json +6 -6
  138. package/data/docs/types/PasswordInput.json +100 -100
  139. package/data/docs/types/PasswordInputField.json +100 -100
  140. package/data/docs/types/ProgressBar.json +13 -13
  141. package/data/docs/types/RadioInputOption.json +56 -56
  142. package/data/docs/types/RadioInputRoot.json +48 -48
  143. package/data/docs/types/RangeCalendar.json +58 -58
  144. package/data/docs/types/RangeSlider.json +337 -0
  145. package/data/docs/types/RangeSliderProps.json +9 -0
  146. package/data/docs/types/Region.json +6 -6
  147. package/data/docs/types/RegionTarget.json +6 -6
  148. package/data/docs/types/RichTextInput.json +2 -2
  149. package/data/docs/types/Root.json +11 -11
  150. package/data/docs/types/Row.json +6 -6
  151. package/data/docs/types/ScrollArea.json +2 -2
  152. package/data/docs/types/SearchInput.json +106 -106
  153. package/data/docs/types/SearchInputField.json +102 -102
  154. package/data/docs/types/SelectOption.json +54 -54
  155. package/data/docs/types/SelectOptionGroup.json +18 -18
  156. package/data/docs/types/SelectOptions.json +64 -64
  157. package/data/docs/types/SelectRoot.json +92 -92
  158. package/data/docs/types/Separator.json +4 -4
  159. package/data/docs/types/SimpleGrid.json +28 -28
  160. package/data/docs/types/Skeleton.json +129 -0
  161. package/data/docs/types/SkeletonCircle.json +103 -0
  162. package/data/docs/types/SkeletonCircleProps.json +9 -0
  163. package/data/docs/types/SkeletonProps.json +9 -0
  164. package/data/docs/types/SkeletonText.json +62 -0
  165. package/data/docs/types/SkeletonTextProps.json +9 -0
  166. package/data/docs/types/Slider.json +337 -0
  167. package/data/docs/types/SlidingIndicatorGeometry.json +9 -0
  168. package/data/docs/types/SlidingIndicatorRects.json +9 -0
  169. package/data/docs/types/SplitButton.json +16 -20
  170. package/data/docs/types/Stack.json +2 -2
  171. package/data/docs/types/StepsRoot.json +2 -2
  172. package/data/docs/types/Switch.json +34 -34
  173. package/data/docs/types/TabNavItem.json +5 -5
  174. package/data/docs/types/TabNavRoot.json +5 -5
  175. package/data/docs/types/TableBody.json +6 -6
  176. package/data/docs/types/TableCaption.json +6 -6
  177. package/data/docs/types/TableCell.json +6 -6
  178. package/data/docs/types/TableColumn.json +6 -6
  179. package/data/docs/types/TableColumnGroup.json +6 -6
  180. package/data/docs/types/TableColumnHeader.json +6 -6
  181. package/data/docs/types/TableFooter.json +6 -6
  182. package/data/docs/types/TableHeader.json +6 -6
  183. package/data/docs/types/TableRoot.json +19 -19
  184. package/data/docs/types/TableRow.json +6 -6
  185. package/data/docs/types/TableScrollArea.json +6 -6
  186. package/data/docs/types/TabsList.json +2 -2
  187. package/data/docs/types/TabsPanel.json +2 -2
  188. package/data/docs/types/TabsPanels.json +2 -2
  189. package/data/docs/types/TabsRoot.json +3 -3
  190. package/data/docs/types/TabsTab.json +2 -2
  191. package/data/docs/types/TagGroupRoot.json +24 -24
  192. package/data/docs/types/TagGroupTag.json +56 -56
  193. package/data/docs/types/TagGroupTagList.json +14 -14
  194. package/data/docs/types/Text.json +8 -8
  195. package/data/docs/types/TextInput.json +102 -102
  196. package/data/docs/types/TextInputField.json +100 -100
  197. package/data/docs/types/TimeInput.json +68 -68
  198. package/data/docs/types/ToggleButton.json +73 -73
  199. package/data/docs/types/ToggleButtonGroupButton.json +30 -30
  200. package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
  201. package/data/docs/types/Toolbar.json +9 -9
  202. package/data/docs/types/TooltipContent.json +28 -28
  203. package/data/docs/types/TooltipRoot.json +16 -16
  204. package/data/docs/types/Tree.json +12 -0
  205. package/data/docs/types/TreeIndicator.json +828 -0
  206. package/data/docs/types/TreeItem.json +559 -0
  207. package/data/docs/types/TreeItemContent.json +27 -0
  208. package/data/docs/types/TreeRoot.json +636 -0
  209. package/data/docs/types/TreeSubTree.json +40 -0
  210. package/data/docs/types/VisuallyHidden.json +6 -6
  211. package/data/docs/types/createItemsFromCsvDrop.json +4 -4
  212. package/data/docs/types/createItemsFromDirectoryDrop.json +4 -4
  213. package/data/docs/types/createItemsFromFileDrop.json +4 -4
  214. package/data/docs/types/createItemsFromImageDrop.json +4 -4
  215. package/data/docs/types/createItemsFromJsonDrop.json +4 -4
  216. package/data/docs/types/manifest.json +50 -0
  217. package/data/docs/types/useRegion.json +2 -2
  218. package/data/docs/types/useSlidingIndicator.json +145 -0
  219. package/data/docs/types/useTree.json +267 -0
  220. package/data/tokens.json +21 -0
  221. package/data/uikit-tokens.json +894 -0
  222. package/dist/index.js +3298 -109
  223. package/package.json +8 -6
@@ -0,0 +1,633 @@
1
+ {
2
+ "meta": {
3
+ "id": "Components-RangeSlider",
4
+ "title": "Range Slider",
5
+ "exportName": "RangeSlider",
6
+ "description": "A range slider lets users select a numeric range — a lower and an upper bound — by dragging two handles along a shared track.",
7
+ "lifecycleState": "Beta",
8
+ "order": 999,
9
+ "repoPath": "packages/nimbus/src/components/slider/derivatives/range-slider/range-slider.mdx",
10
+ "menu": [
11
+ "Components",
12
+ "Inputs",
13
+ "Range Slider"
14
+ ],
15
+ "route": "components/inputs/range-slider",
16
+ "tags": [
17
+ "component",
18
+ "range slider",
19
+ "slider",
20
+ "input",
21
+ "numeric",
22
+ "range"
23
+ ],
24
+ "toc": [
25
+ {
26
+ "value": "Overview",
27
+ "href": "#overview",
28
+ "depth": 2,
29
+ "numbering": [
30
+ 1,
31
+ 1
32
+ ],
33
+ "parent": "root"
34
+ },
35
+ {
36
+ "value": "Key features",
37
+ "href": "#key-features",
38
+ "depth": 3,
39
+ "numbering": [
40
+ 1,
41
+ 1,
42
+ 1
43
+ ],
44
+ "parent": "root"
45
+ },
46
+ {
47
+ "value": "Resources",
48
+ "href": "#resources",
49
+ "depth": 3,
50
+ "numbering": [
51
+ 1,
52
+ 1,
53
+ 2
54
+ ],
55
+ "parent": "root"
56
+ },
57
+ {
58
+ "value": "Variables",
59
+ "href": "#variables",
60
+ "depth": 2,
61
+ "numbering": [
62
+ 1,
63
+ 2
64
+ ],
65
+ "parent": "root"
66
+ },
67
+ {
68
+ "value": "Thumb labels",
69
+ "href": "#thumb-labels",
70
+ "depth": 3,
71
+ "numbering": [
72
+ 1,
73
+ 2,
74
+ 1
75
+ ],
76
+ "parent": "root"
77
+ },
78
+ {
79
+ "value": "Size",
80
+ "href": "#size",
81
+ "depth": 3,
82
+ "numbering": [
83
+ 1,
84
+ 2,
85
+ 2
86
+ ],
87
+ "parent": "root"
88
+ },
89
+ {
90
+ "value": "Variants",
91
+ "href": "#variants",
92
+ "depth": 3,
93
+ "numbering": [
94
+ 1,
95
+ 2,
96
+ 3
97
+ ],
98
+ "parent": "root"
99
+ },
100
+ {
101
+ "value": "Orientation",
102
+ "href": "#orientation",
103
+ "depth": 3,
104
+ "numbering": [
105
+ 1,
106
+ 2,
107
+ 4
108
+ ],
109
+ "parent": "root"
110
+ },
111
+ {
112
+ "value": "Ticks",
113
+ "href": "#ticks",
114
+ "depth": 3,
115
+ "numbering": [
116
+ 1,
117
+ 2,
118
+ 5
119
+ ],
120
+ "parent": "root"
121
+ }
122
+ ],
123
+ "layout": "app-frame",
124
+ "tabs": [
125
+ {
126
+ "key": "overview",
127
+ "title": "Overview",
128
+ "order": 0
129
+ },
130
+ {
131
+ "key": "guidelines",
132
+ "title": "Guidelines",
133
+ "order": 2
134
+ },
135
+ {
136
+ "key": "dev",
137
+ "title": "Implementation",
138
+ "order": 3
139
+ },
140
+ {
141
+ "key": "a11y",
142
+ "title": "Accessibility",
143
+ "order": 4
144
+ }
145
+ ]
146
+ },
147
+ "mdx": "\n## Overview\n\nA range slider lets people choose a range — a lower and an upper bound — by\ndragging two handles along a shared track, instead of typing two numbers. It's a\ngood fit whenever the exact numbers matter less than their position relative to\nthe whole scale, such as a price, distance, or rating filter.\n\n`RangeSlider` selects a `[min, max]` pair with two handles that can't cross each\nother, so the lower value never exceeds the upper one. For selecting a single\nvalue, use [`Slider`](/components/inputs/slider) instead — the two share the\nsame track, handle, and tick styling, so a page that mixes single and range\nsliders still looks consistent.\n\nLike `Slider`, `RangeSlider` draws no label or static \"current value\" readout of\nits own. Each handle's value appears in a small tooltip above it while it's\nhovered, focused, or being dragged — the track stays visually quiet the rest of\nthe time. The control's label comes from the surrounding `FormField`, or from an\n`aria-label` when it's used on its own, and each handle takes its own name from\n`thumbLabels`.\n\n### Key features\n\n- **Two non-crossing handles:** the lower handle can't be dragged past the upper\n one, so the selection is always a valid `[min, max]` range.\n- **Tooltip value display:** each handle's value shows above it on hover, focus,\n or drag — there's no permanent number next to the track.\n- **Per-handle names:** `thumbLabels` gives each handle its own accessible name;\n when omitted, the handles fall back to localized \"Minimum\" / \"Maximum\" labels.\n- **Shared styling with Slider:** the same size, variant, orientation, and tick\n options apply, so range and single sliders stay visually consistent.\n\n### Resources\n\n- [Slider](/components/inputs/slider) — the single-value counterpart\n- [W3C ARIA Authoring Practices: Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/)\n\n## Variables\n\n### Thumb labels\n\n`RangeSlider` uses two handles, so each one needs its own accessible name. Pass\n`thumbLabels` as a `[lower, upper]` pair (for example\n`[\"Minimum price\", \"Maximum price\"]`); when omitted, the handles fall back to\nlocalized \"Minimum\" / \"Maximum\" labels rather than being left anonymous.\n\n```jsx live\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n);\n```\n\n### Size\n\n`md` is the default size. `sm` is available for denser layouts.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"center\">\n <RangeSlider aria-label=\"Small\" size=\"sm\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Medium (default)\"\n size=\"md\"\n defaultValue={[20, 60]}\n />\n </Stack>\n);\n```\n\n### Variants\n\n`filled` is the default. Two additional variants are available for contexts that\ncall for a different visual weight:\n\n- **Filled** (default) — a standard thin track with a colored progress fill\n between the two handles. The safest choice for most forms and filters.\n- **Minimal** — a thin neutral track with single-color handles and a colored\n range segment between them; the track and range grow to full width on hover or\n focus. For low-emphasis controls where a full colored bar would over-emphasize\n the range.\n- **Enclosed** — a thick, contained bar with the handles inset inside it (an\n iOS-style control). Good for touch-first contexts or standalone controls that\n benefit from larger, more tactile targets.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"center\">\n <RangeSlider\n aria-label=\"Filled (default)\"\n variant=\"filled\"\n defaultValue={[20, 60]}\n />\n <RangeSlider aria-label=\"Minimal\" variant=\"minimal\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Enclosed\"\n variant=\"enclosed\"\n defaultValue={[25, 75]}\n />\n </Stack>\n);\n```\n\n### Orientation\n\n`horizontal` is the default. `vertical` is useful when horizontal space is\nlimited. The vertical track has a built-in default length, so no explicit\n`height` is required.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"flex-start\">\n <RangeSlider aria-label=\"Horizontal\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Vertical\"\n defaultValue={[20, 60]}\n orientation=\"vertical\"\n />\n </Stack>\n);\n```\n\n### Ticks\n\nTurn on `showTicks` to mark discrete steps along the track. `tickStep` (or\n`step`, if `tickStep` is omitted) controls how far apart the ticks are. Ticks\nare purely visual — they don't constrain where the handles can land.\n\n```jsx live\nconst App = () => (\n <RangeSlider\n aria-label=\"Rating range\"\n defaultValue={[25, 75]}\n minValue={0}\n maxValue={100}\n step={25}\n showTicks\n />\n);\n```\n",
148
+ "views": {
149
+ "overview": {
150
+ "mdx": "\n## Overview\n\nA range slider lets people choose a range — a lower and an upper bound — by\ndragging two handles along a shared track, instead of typing two numbers. It's a\ngood fit whenever the exact numbers matter less than their position relative to\nthe whole scale, such as a price, distance, or rating filter.\n\n`RangeSlider` selects a `[min, max]` pair with two handles that can't cross each\nother, so the lower value never exceeds the upper one. For selecting a single\nvalue, use [`Slider`](/components/inputs/slider) instead — the two share the\nsame track, handle, and tick styling, so a page that mixes single and range\nsliders still looks consistent.\n\nLike `Slider`, `RangeSlider` draws no label or static \"current value\" readout of\nits own. Each handle's value appears in a small tooltip above it while it's\nhovered, focused, or being dragged — the track stays visually quiet the rest of\nthe time. The control's label comes from the surrounding `FormField`, or from an\n`aria-label` when it's used on its own, and each handle takes its own name from\n`thumbLabels`.\n\n### Key features\n\n- **Two non-crossing handles:** the lower handle can't be dragged past the upper\n one, so the selection is always a valid `[min, max]` range.\n- **Tooltip value display:** each handle's value shows above it on hover, focus,\n or drag — there's no permanent number next to the track.\n- **Per-handle names:** `thumbLabels` gives each handle its own accessible name;\n when omitted, the handles fall back to localized \"Minimum\" / \"Maximum\" labels.\n- **Shared styling with Slider:** the same size, variant, orientation, and tick\n options apply, so range and single sliders stay visually consistent.\n\n### Resources\n\n- [Slider](/components/inputs/slider) — the single-value counterpart\n- [W3C ARIA Authoring Practices: Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/)\n\n## Variables\n\n### Thumb labels\n\n`RangeSlider` uses two handles, so each one needs its own accessible name. Pass\n`thumbLabels` as a `[lower, upper]` pair (for example\n`[\"Minimum price\", \"Maximum price\"]`); when omitted, the handles fall back to\nlocalized \"Minimum\" / \"Maximum\" labels rather than being left anonymous.\n\n```jsx live\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n);\n```\n\n### Size\n\n`md` is the default size. `sm` is available for denser layouts.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"center\">\n <RangeSlider aria-label=\"Small\" size=\"sm\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Medium (default)\"\n size=\"md\"\n defaultValue={[20, 60]}\n />\n </Stack>\n);\n```\n\n### Variants\n\n`filled` is the default. Two additional variants are available for contexts that\ncall for a different visual weight:\n\n- **Filled** (default) — a standard thin track with a colored progress fill\n between the two handles. The safest choice for most forms and filters.\n- **Minimal** — a thin neutral track with single-color handles and a colored\n range segment between them; the track and range grow to full width on hover or\n focus. For low-emphasis controls where a full colored bar would over-emphasize\n the range.\n- **Enclosed** — a thick, contained bar with the handles inset inside it (an\n iOS-style control). Good for touch-first contexts or standalone controls that\n benefit from larger, more tactile targets.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"center\">\n <RangeSlider\n aria-label=\"Filled (default)\"\n variant=\"filled\"\n defaultValue={[20, 60]}\n />\n <RangeSlider aria-label=\"Minimal\" variant=\"minimal\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Enclosed\"\n variant=\"enclosed\"\n defaultValue={[25, 75]}\n />\n </Stack>\n);\n```\n\n### Orientation\n\n`horizontal` is the default. `vertical` is useful when horizontal space is\nlimited. The vertical track has a built-in default length, so no explicit\n`height` is required.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"horizontal\" gap=\"600\" alignItems=\"flex-start\">\n <RangeSlider aria-label=\"Horizontal\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Vertical\"\n defaultValue={[20, 60]}\n orientation=\"vertical\"\n />\n </Stack>\n);\n```\n\n### Ticks\n\nTurn on `showTicks` to mark discrete steps along the track. `tickStep` (or\n`step`, if `tickStep` is omitted) controls how far apart the ticks are. Ticks\nare purely visual — they don't constrain where the handles can land.\n\n```jsx live\nconst App = () => (\n <RangeSlider\n aria-label=\"Rating range\"\n defaultValue={[25, 75]}\n minValue={0}\n maxValue={100}\n step={25}\n showTicks\n />\n);\n```\n",
151
+ "toc": [
152
+ {
153
+ "value": "Overview",
154
+ "href": "#overview",
155
+ "depth": 2,
156
+ "numbering": [
157
+ 1,
158
+ 1
159
+ ],
160
+ "parent": "root"
161
+ },
162
+ {
163
+ "value": "Key features",
164
+ "href": "#key-features",
165
+ "depth": 3,
166
+ "numbering": [
167
+ 1,
168
+ 1,
169
+ 1
170
+ ],
171
+ "parent": "root"
172
+ },
173
+ {
174
+ "value": "Resources",
175
+ "href": "#resources",
176
+ "depth": 3,
177
+ "numbering": [
178
+ 1,
179
+ 1,
180
+ 2
181
+ ],
182
+ "parent": "root"
183
+ },
184
+ {
185
+ "value": "Variables",
186
+ "href": "#variables",
187
+ "depth": 2,
188
+ "numbering": [
189
+ 1,
190
+ 2
191
+ ],
192
+ "parent": "root"
193
+ },
194
+ {
195
+ "value": "Thumb labels",
196
+ "href": "#thumb-labels",
197
+ "depth": 3,
198
+ "numbering": [
199
+ 1,
200
+ 2,
201
+ 1
202
+ ],
203
+ "parent": "root"
204
+ },
205
+ {
206
+ "value": "Size",
207
+ "href": "#size",
208
+ "depth": 3,
209
+ "numbering": [
210
+ 1,
211
+ 2,
212
+ 2
213
+ ],
214
+ "parent": "root"
215
+ },
216
+ {
217
+ "value": "Variants",
218
+ "href": "#variants",
219
+ "depth": 3,
220
+ "numbering": [
221
+ 1,
222
+ 2,
223
+ 3
224
+ ],
225
+ "parent": "root"
226
+ },
227
+ {
228
+ "value": "Orientation",
229
+ "href": "#orientation",
230
+ "depth": 3,
231
+ "numbering": [
232
+ 1,
233
+ 2,
234
+ 4
235
+ ],
236
+ "parent": "root"
237
+ },
238
+ {
239
+ "value": "Ticks",
240
+ "href": "#ticks",
241
+ "depth": 3,
242
+ "numbering": [
243
+ 1,
244
+ 2,
245
+ 5
246
+ ],
247
+ "parent": "root"
248
+ }
249
+ ]
250
+ },
251
+ "a11y": {
252
+ "mdx": "\n## Accessibility\n\nAccessibility ensures digital content and functionality are usable by everyone,\nincluding people with disabilities, by addressing visual, auditory, cognitive,\nand physical limitations.\n\n`RangeSlider` renders no visible label of its own, so the control's accessible\nname must always come from somewhere — either `FormField.Label` composed inside\na form field, or an `aria-label` / `aria-labelledby` when used standalone. On top\nof that, each of the two handles needs its own name via `thumbLabels`.\n\n```jsx live\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n);\n```\n\n### Accessibility standards\n\n- Always give the range slider an accessible name: `aria-label` (or\n `aria-labelledby`) when standalone, or `FormField.Label` when composed inside\n `FormField.Root`. A slider with no accessible name is unusable with a screen\n reader.\n- Give each handle its own name via `thumbLabels` (e.g.\n `[\"Minimum price\", \"Maximum price\"]`); when omitted, the handles fall back to\n localized \"Minimum\" / \"Maximum\" labels so they're never anonymous.\n- Each handle's current value is exposed to assistive technology through its\n native range value, not just the visual tooltip — screen readers announce it\n without needing the tooltip open.\n- Support full keyboard interaction: `Tab` moves focus between the two handles,\n `Arrow` keys adjust the focused handle by `step`, and `Home` / `End` jump it to\n its currently allowed minimum / maximum.\n- Ensure sufficient color contrast for the track, fill, and handles in every\n state (default, hover, focus, dragging, disabled, invalid).\n- Communicate an invalid state with both a visual cue and a `FormField.Error`\n message — never color alone.\n- The invalid visual cue comes from `data-invalid` on the root element, not\n `aria-invalid` on the underlying range inputs — React Aria's `Slider` has no\n validation-state concept of its own (unlike text-style inputs). Assistive\n technology instead picks up the `FormField.Error` message.\n\n### Keyboard interactions\n\n`RangeSlider` has two independently focusable handles.\n\n| Key | Action |\n| ---------------------------------- | ----------------------------------------------------------------- |\n| `Tab` / `Shift+Tab` | Move focus to / between / away from the two handles. |\n| `Arrow Left` / `Arrow Right` | Decrement / increment the focused handle by `step` (horizontal). |\n| `Arrow Down` / `Arrow Up` | Decrement / increment the focused handle by `step` (vertical). |\n| `Page Down` / `Page Up` | Decrement / increment the focused handle by a larger amount. |\n| `Home` | Jump the focused handle to its currently allowed minimum. |\n| `End` | Jump the focused handle to its currently allowed maximum. |\n\nA handle is never moved past the other one — the effective range is clamped to\n`[minValue, upperValue]` for the lower handle and `[lowerValue, maxValue]` for\nthe upper handle.\n\n### Resources\n\n- [React Aria Slider](https://react-spectrum.adobe.com/react-aria/Slider.html)\n- [W3C ARIA Authoring Practices Guide (APG): Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/)\n",
253
+ "toc": [
254
+ {
255
+ "value": "Accessibility",
256
+ "href": "#accessibility",
257
+ "depth": 2,
258
+ "numbering": [
259
+ 1,
260
+ 1
261
+ ],
262
+ "parent": "root"
263
+ },
264
+ {
265
+ "value": "Accessibility standards",
266
+ "href": "#accessibility-standards",
267
+ "depth": 3,
268
+ "numbering": [
269
+ 1,
270
+ 1,
271
+ 1
272
+ ],
273
+ "parent": "root"
274
+ },
275
+ {
276
+ "value": "Keyboard interactions",
277
+ "href": "#keyboard-interactions",
278
+ "depth": 3,
279
+ "numbering": [
280
+ 1,
281
+ 1,
282
+ 2
283
+ ],
284
+ "parent": "root"
285
+ },
286
+ {
287
+ "value": "Resources",
288
+ "href": "#resources",
289
+ "depth": 3,
290
+ "numbering": [
291
+ 1,
292
+ 1,
293
+ 3
294
+ ],
295
+ "parent": "root"
296
+ }
297
+ ]
298
+ },
299
+ "dev": {
300
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { RangeSlider, type RangeSliderProps } from \"@commercetools/nimbus\";\n```\n\n### Basic usage\n\n`RangeSlider` selects a `[number, number]` tuple using two handles that can't be\ndragged past each other — the lower value never exceeds the upper one. It renders\nno visible label and no static value readout — each handle's current value\nappears in a tooltip above it while it's hovered, focused, or dragged, so an\naccessible name must be supplied separately (see [Accessibility](#accessibility)\nbelow).\n\nUse `thumbLabels` to give each handle its own accessible name; when omitted, the\nhandles fall back to localized \"Minimum\" / \"Maximum\" labels.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum\", \"Maximum\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n);\n```\n\n> For selecting a single value rather than a range, use the\n> [`Slider`](/components/inputs/slider) component. The two share the same track,\n> handle, and tick styling.\n\n## Usage examples\n\n### Thumb labels\n\n`thumbLabels` is a `[lower, upper]` array that names each handle individually.\nPrefer domain-specific labels over the generic fallbacks.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n);\n```\n\n### Size options\n\n`sm` and `md` size variants are available to match the surrounding density.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <RangeSlider aria-label=\"Small\" size=\"sm\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Medium (default)\"\n size=\"md\"\n defaultValue={[20, 60]}\n />\n </Stack>\n);\n```\n\n### Visual variants\n\n`variant` changes the track/progress/thumb treatment. `filled` (the default) is a\nstandard thin track with a colored progress fill; `minimal` is a thin neutral\ntrack with single-color thumbs and a colored range segment that grow to full\nwidth on hover/focus, for low-emphasis controls such as a thumbnail-size picker;\n`enclosed` is a thick, contained bar with the\nthumbs inset inside it. This applies to both `RangeSlider` and `Slider` since\nthey share the same recipe.\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"600\">\n <RangeSlider aria-label=\"Filled\" variant=\"filled\" defaultValue={[20, 60]} />\n <RangeSlider aria-label=\"Minimal\" variant=\"minimal\" defaultValue={[20, 60]} />\n <RangeSlider\n aria-label=\"Enclosed\"\n variant=\"enclosed\"\n defaultValue={[25, 75]}\n />\n </Stack>\n);\n```\n\n### Orientation\n\nSet `orientation=\"vertical\"` for a vertical track. The vertical track has a\nbuilt-in default length, so no explicit `height` is required.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Volume range\"\n defaultValue={[20, 60]}\n orientation=\"vertical\"\n />\n);\n```\n\n### Ticks\n\nTurn on `showTicks` to render tick marks at every `tickStep` interval (falling\nback to `step` when `tickStep` is omitted). Useful for scales where the discrete\nstops themselves are meaningful.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Rating range\"\n defaultValue={[25, 75]}\n minValue={0}\n maxValue={100}\n step={25}\n showTicks\n />\n);\n```\n\n**Behavioral notes:**\n\n- `tickStep` controls the spacing of ticks; it defaults to `step` when\n `showTicks` is set and `tickStep` is omitted.\n- Ticks are purely visual — they don't constrain the values a handle can land\n on. Use `step` for that.\n- Ticks are spaced evenly starting at `minValue`. When `(maxValue - minValue)`\n isn't evenly divisible by the tick step, the last tick lands on the final full\n step short of `maxValue`, not on `maxValue` itself.\n\n### Value formatting\n\n`formatOptions` (an `Intl.NumberFormatOptions` object) formats the value shown in\neach handle's tooltip, without changing the underlying numeric values.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 120]}\n minValue={0}\n maxValue={200}\n formatOptions={{ style: \"currency\", currency: \"USD\" }}\n />\n);\n```\n\n### Disabled state\n\nDisable interaction with `isDisabled`.\n\n```jsx live-dev\nconst App = () => (\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n isDisabled\n />\n);\n```\n\n### Uncontrolled mode\n\nFor simpler use cases, use uncontrolled mode with `defaultValue` and `onChange`.\nThe `Text` element below stands in for **your own** application code reacting to\nthe live range (e.g. updating a preview elsewhere on the page) — it's a demo of\nwhat `onChange` gives you, not the recommended \"value readout beside the track\"\npattern; each handle's own tooltip already shows its value on hover, focus, and\ndrag (see the Guidelines tab).\n\n```jsx live-dev\nconst App = () => {\n const [range, setRange] = useState([20, 60]);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum\", \"Maximum\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n onChange={setRange}\n />\n <Text fontSize=\"sm\">\n Current range (for demo purposes): {range[0]} – {range[1]}\n </Text>\n </Stack>\n );\n};\n```\n\n### Controlled mode\n\nFor scenarios requiring programmatic control, use controlled mode. The value is a\n`[number, number]` tuple; `onChange` receives the updated tuple.\n\n```jsx live-dev\nconst App = () => {\n const [range, setRange] = useState([20, 60]);\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum\", \"Maximum\"]}\n value={range}\n onChange={setRange}\n minValue={0}\n maxValue={100}\n />\n <Stack direction=\"row\" gap=\"300\">\n <Button onPress={() => setRange([0, 100])}>Reset</Button>\n <Button onPress={() => setRange([40, 60])}>Narrow</Button>\n </Stack>\n </Stack>\n );\n};\n```\n\n## Component requirements\n\n### Accessibility\n\n`RangeSlider` handles keyboard, pointer, touch, and RTL behavior internally via\nReact Aria. However, unlike most Nimbus inputs, **it renders no visible label and\nno static value readout** — each handle's current value is only ever shown in a\ntooltip above it while it's hovered, focused, or dragged. You must always supply\nan accessible name for the control through one of the following:\n\n- Composing inside `FormField.Root` with `FormField.Label` (recommended in\n forms):\n\n```tsx\n<FormField.Root>\n <FormField.Label>Price range</FormField.Label>\n <FormField.Input>\n <RangeSlider\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 120]}\n minValue={0}\n maxValue={200}\n />\n </FormField.Input>\n</FormField.Root>\n```\n\n- Passing `aria-label` directly, for standalone use where no visible label is\n part of the design:\n\n```tsx\n<RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 120]}\n minValue={0}\n maxValue={200}\n/>\n```\n\n- Associating an existing `<label>` element via `aria-labelledby`:\n\n```tsx\n<label id=\"price-label\">Price range</label>\n<RangeSlider\n aria-labelledby=\"price-label\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 120]}\n minValue={0}\n maxValue={200}\n/>\n```\n\nOn top of the control's name, use `thumbLabels` to name each handle\nindividually (e.g. `[\"Minimum price\", \"Maximum price\"]`); if omitted, the handles\nfall back to localized \"Minimum\" / \"Maximum\" labels rather than being left\nanonymous.\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction, with two independently\nfocusable handles:\n\n- `Tab` / `Shift+Tab`: move focus to, between, and away from the two handles.\n- `Arrow Left` / `Arrow Right` (or `Arrow Down` / `Arrow Up` when\n `orientation=\"vertical\"`): decrement/increment the focused handle's value by\n `step`.\n- `Page Down` / `Page Up`: decrement/increment the focused handle by a larger\n amount.\n- `Home`: jump the focused handle to its currently allowed minimum.\n- `End`: jump the focused handle to its currently allowed maximum.\n\nA handle is never moved past the other one — the effective range is clamped to\n`[minValue, upperValue]` for the lower handle and `[lowerValue, maxValue]` for\nthe upper handle.\n\n## API reference\n\n<PropsTable id=\"RangeSlider\" />\n\n## Common patterns\n\n### Price range filter\n\nA typical use of `RangeSlider` pairs it with a `FormField.Description` that\nsummarizes the current selection. Unlike a bare value readout placed directly\nbeside the track (see Guidelines), the description is a permanent part of the\nform field's own text stack, below the control — useful context for anyone who\nisn't actively hovering or focusing a handle, rather than a duplicate of the\ntooltip.\n\n```jsx live-dev\nconst App = () => {\n const [range, setRange] = useState([20, 80]);\n\n return (\n <Stack direction=\"column\" gap=\"300\">\n <FormField.Root>\n <FormField.Label>Price range</FormField.Label>\n <FormField.Input>\n <RangeSlider\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n value={range}\n onChange={setRange}\n minValue={0}\n maxValue={200}\n formatOptions={{ style: \"currency\", currency: \"USD\" }}\n />\n </FormField.Input>\n <FormField.Description>\n ${range[0]} – ${range[1]}\n </FormField.Description>\n </FormField.Root>\n </Stack>\n );\n};\n```\n\n### RangeSlider inside a form field with validation\n\n`FormField.Input` clones its React-Aria-named props (`aria-labelledby`,\n`aria-describedby`, `isInvalid`, `isDisabled`, `isRequired`, `isReadOnly`) onto\nthe slider, so the label, description, and error state all associate\nautomatically.\n\n```jsx live-dev\nconst App = () => (\n <FormField.Root isInvalid>\n <FormField.Label>Price range</FormField.Label>\n <FormField.Input>\n <RangeSlider\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 120]}\n minValue={0}\n maxValue={200}\n />\n </FormField.Input>\n <FormField.Description>Set the price bounds to filter by</FormField.Description>\n <FormField.Error>Range is out of bounds</FormField.Error>\n </FormField.Root>\n);\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using\n`RangeSlider` within your application. As the component's internal functionality\nis already tested by Nimbus, these patterns help you verify your integration and\napplication-specific logic.\n\n### Basic Rendering Tests\n\nVerify the range slider renders two handles with the expected labels and values\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { RangeSlider, FormField, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"RangeSlider - Basic rendering\", () => {\n it(\"renders two handles with localized default labels when thumbLabels is omitted\", () => {\n render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n </NimbusProvider>\n );\n\n const thumbs = screen.getAllByRole(\"slider\");\n expect(thumbs).toHaveLength(2);\n expect(thumbs[0]).toHaveValue(\"20\");\n expect(thumbs[1]).toHaveValue(\"60\");\n expect(thumbs[0]).toHaveAttribute(\"aria-label\", \"Minimum\");\n expect(thumbs[1]).toHaveAttribute(\"aria-label\", \"Maximum\");\n });\n\n it(\"uses custom thumbLabels when provided\", () => {\n render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n </NimbusProvider>\n );\n\n const thumbs = screen.getAllByRole(\"slider\");\n expect(thumbs[0]).toHaveAttribute(\"aria-label\", \"Minimum price\");\n expect(thumbs[1]).toHaveAttribute(\"aria-label\", \"Maximum price\");\n });\n});\n```\n\n### Interaction Tests\n\nTest keyboard interactions, the [number, number] onChange payload, and the non-crossing constraint\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { RangeSlider, FormField, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"RangeSlider - Interactions\", () => {\n it(\"calls onChange with a [number, number] tuple\", async () => {\n const user = userEvent.setup();\n const handleChange = vi.fn();\n render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n onChange={handleChange}\n />\n </NimbusProvider>\n );\n\n const thumbs = screen.getAllByRole(\"slider\");\n thumbs[0].focus();\n await user.keyboard(\"{ArrowRight}\");\n\n expect(handleChange).toHaveBeenCalledWith([21, 60]);\n });\n\n it(\"keeps the lower handle from crossing the upper handle\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n </NimbusProvider>\n );\n\n const thumbs = screen.getAllByRole(\"slider\");\n thumbs[0].focus();\n await user.keyboard(\"{End}\");\n\n const lower = Number((thumbs[0] as HTMLInputElement).value);\n const upper = Number((thumbs[1] as HTMLInputElement).value);\n expect(lower).toBeLessThanOrEqual(upper);\n });\n});\n```\n\n### Testing States\n\nVerify disabled and invalid states render correctly\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { RangeSlider, FormField, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"RangeSlider - States\", () => {\n it(\"handles disabled state on both handles\", () => {\n render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n isDisabled\n />\n </NimbusProvider>\n );\n\n const thumbs = screen.getAllByRole(\"slider\");\n expect(thumbs).toHaveLength(2);\n thumbs.forEach((thumb) => expect(thumb).toBeDisabled());\n });\n\n it(\"surfaces the invalid state on the root element\", () => {\n const { container } = render(\n <NimbusProvider>\n <RangeSlider\n aria-label=\"Price range\"\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n isInvalid\n />\n </NimbusProvider>\n );\n\n const root = container.querySelector('[data-slot=\"root\"]');\n expect(root).toHaveAttribute(\"data-invalid\", \"true\");\n });\n});\n```\n\n### FormField Integration Tests\n\nVerify the range slider composes correctly inside FormField\n\n```tsx\nimport { describe, it, expect, vi } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { RangeSlider, FormField, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"RangeSlider - FormField integration\", () => {\n it(\"surfaces the invalid state from FormField.Root\", () => {\n const { container } = render(\n <NimbusProvider>\n <FormField.Root isInvalid>\n <FormField.Label>Price range</FormField.Label>\n <FormField.Input>\n <RangeSlider\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n defaultValue={[20, 60]}\n minValue={0}\n maxValue={100}\n />\n </FormField.Input>\n <FormField.Error>Range is out of bounds</FormField.Error>\n </FormField.Root>\n </NimbusProvider>\n );\n\n const root = container.querySelector('[data-slot=\"root\"]');\n expect(root).toHaveAttribute(\"data-invalid\", \"true\");\n });\n});\n```\n\n\n## Resources\n\n- [Storybook: RangeSlider](https://nimbus-storybook.vercel.app/?path=/docs/components-rangeslider--docs)\n- [Slider](/components/inputs/slider) — the single-value counterpart\n- [React Aria Slider](https://react-spectrum.adobe.com/react-aria/Slider.html)\n- [W3C ARIA Authoring Practices Guide (APG): Slider (Multi-Thumb) pattern](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/)\n",
301
+ "toc": [
302
+ {
303
+ "value": "Getting started",
304
+ "href": "#getting-started",
305
+ "depth": 2,
306
+ "numbering": [
307
+ 1,
308
+ 1
309
+ ],
310
+ "parent": "root"
311
+ },
312
+ {
313
+ "value": "Import",
314
+ "href": "#import",
315
+ "depth": 3,
316
+ "numbering": [
317
+ 1,
318
+ 1,
319
+ 1
320
+ ],
321
+ "parent": "root"
322
+ },
323
+ {
324
+ "value": "Basic usage",
325
+ "href": "#basic-usage",
326
+ "depth": 3,
327
+ "numbering": [
328
+ 1,
329
+ 1,
330
+ 2
331
+ ],
332
+ "parent": "root"
333
+ },
334
+ {
335
+ "value": "Usage examples",
336
+ "href": "#usage-examples",
337
+ "depth": 2,
338
+ "numbering": [
339
+ 1,
340
+ 2
341
+ ],
342
+ "parent": "root"
343
+ },
344
+ {
345
+ "value": "Thumb labels",
346
+ "href": "#thumb-labels",
347
+ "depth": 3,
348
+ "numbering": [
349
+ 1,
350
+ 2,
351
+ 1
352
+ ],
353
+ "parent": "root"
354
+ },
355
+ {
356
+ "value": "Size options",
357
+ "href": "#size-options",
358
+ "depth": 3,
359
+ "numbering": [
360
+ 1,
361
+ 2,
362
+ 2
363
+ ],
364
+ "parent": "root"
365
+ },
366
+ {
367
+ "value": "Visual variants",
368
+ "href": "#visual-variants",
369
+ "depth": 3,
370
+ "numbering": [
371
+ 1,
372
+ 2,
373
+ 3
374
+ ],
375
+ "parent": "root"
376
+ },
377
+ {
378
+ "value": "Orientation",
379
+ "href": "#orientation",
380
+ "depth": 3,
381
+ "numbering": [
382
+ 1,
383
+ 2,
384
+ 4
385
+ ],
386
+ "parent": "root"
387
+ },
388
+ {
389
+ "value": "Ticks",
390
+ "href": "#ticks",
391
+ "depth": 3,
392
+ "numbering": [
393
+ 1,
394
+ 2,
395
+ 5
396
+ ],
397
+ "parent": "root"
398
+ },
399
+ {
400
+ "value": "Value formatting",
401
+ "href": "#value-formatting",
402
+ "depth": 3,
403
+ "numbering": [
404
+ 1,
405
+ 2,
406
+ 6
407
+ ],
408
+ "parent": "root"
409
+ },
410
+ {
411
+ "value": "Disabled state",
412
+ "href": "#disabled-state",
413
+ "depth": 3,
414
+ "numbering": [
415
+ 1,
416
+ 2,
417
+ 7
418
+ ],
419
+ "parent": "root"
420
+ },
421
+ {
422
+ "value": "Uncontrolled mode",
423
+ "href": "#uncontrolled-mode",
424
+ "depth": 3,
425
+ "numbering": [
426
+ 1,
427
+ 2,
428
+ 8
429
+ ],
430
+ "parent": "root"
431
+ },
432
+ {
433
+ "value": "Controlled mode",
434
+ "href": "#controlled-mode",
435
+ "depth": 3,
436
+ "numbering": [
437
+ 1,
438
+ 2,
439
+ 9
440
+ ],
441
+ "parent": "root"
442
+ },
443
+ {
444
+ "value": "Component requirements",
445
+ "href": "#component-requirements",
446
+ "depth": 2,
447
+ "numbering": [
448
+ 1,
449
+ 3
450
+ ],
451
+ "parent": "root"
452
+ },
453
+ {
454
+ "value": "Accessibility",
455
+ "href": "#accessibility",
456
+ "depth": 3,
457
+ "numbering": [
458
+ 1,
459
+ 3,
460
+ 1
461
+ ],
462
+ "parent": "root"
463
+ },
464
+ {
465
+ "value": "Keyboard navigation",
466
+ "href": "#keyboard-navigation",
467
+ "depth": 4,
468
+ "numbering": [
469
+ 1,
470
+ 3,
471
+ 1,
472
+ 1
473
+ ],
474
+ "parent": "root"
475
+ },
476
+ {
477
+ "value": "API reference",
478
+ "href": "#api-reference",
479
+ "depth": 2,
480
+ "numbering": [
481
+ 1,
482
+ 4
483
+ ],
484
+ "parent": "root"
485
+ },
486
+ {
487
+ "value": "Common patterns",
488
+ "href": "#common-patterns",
489
+ "depth": 2,
490
+ "numbering": [
491
+ 1,
492
+ 5
493
+ ],
494
+ "parent": "root"
495
+ },
496
+ {
497
+ "value": "Price range filter",
498
+ "href": "#price-range-filter",
499
+ "depth": 3,
500
+ "numbering": [
501
+ 1,
502
+ 5,
503
+ 1
504
+ ],
505
+ "parent": "root"
506
+ },
507
+ {
508
+ "value": "RangeSlider inside a form field with validation",
509
+ "href": "#rangeslider-inside-a-form-field-with-validation",
510
+ "depth": 3,
511
+ "numbering": [
512
+ 1,
513
+ 5,
514
+ 2
515
+ ],
516
+ "parent": "root"
517
+ },
518
+ {
519
+ "value": "Testing your implementation",
520
+ "href": "#testing-your-implementation",
521
+ "depth": 2,
522
+ "numbering": [
523
+ 1,
524
+ 6
525
+ ],
526
+ "parent": "root"
527
+ },
528
+ {
529
+ "value": "Basic Rendering Tests",
530
+ "href": "#basic-rendering-tests",
531
+ "depth": 3,
532
+ "numbering": [
533
+ 1,
534
+ 6,
535
+ 1
536
+ ],
537
+ "parent": "root"
538
+ },
539
+ {
540
+ "value": "Interaction Tests",
541
+ "href": "#interaction-tests",
542
+ "depth": 3,
543
+ "numbering": [
544
+ 1,
545
+ 6,
546
+ 2
547
+ ],
548
+ "parent": "root"
549
+ },
550
+ {
551
+ "value": "Testing States",
552
+ "href": "#testing-states",
553
+ "depth": 3,
554
+ "numbering": [
555
+ 1,
556
+ 6,
557
+ 3
558
+ ],
559
+ "parent": "root"
560
+ },
561
+ {
562
+ "value": "FormField Integration Tests",
563
+ "href": "#formfield-integration-tests",
564
+ "depth": 3,
565
+ "numbering": [
566
+ 1,
567
+ 6,
568
+ 4
569
+ ],
570
+ "parent": "root"
571
+ },
572
+ {
573
+ "value": "Resources",
574
+ "href": "#resources",
575
+ "depth": 2,
576
+ "numbering": [
577
+ 1,
578
+ 7
579
+ ],
580
+ "parent": "root"
581
+ }
582
+ ]
583
+ },
584
+ "guidelines": {
585
+ "mdx": "\n## Guidelines\n\nRange slider guidelines focus on picking a scale users can reason about, naming\nboth handles clearly, and keeping each value visible while it's being changed,\neven though the control draws no visible label of its own.\n\n### Best practices\n\n- **Name both handles.** Pass `thumbLabels` as a `[lower, upper]` pair (for\n example `[\"Minimum price\", \"Maximum price\"]`) so each handle has a distinct\n accessible name. When omitted, the handles fall back to localized\n \"Minimum\" / \"Maximum\" — usable, but less specific than a domain label.\n- **Give the control an accessible name too.** A range slider renders no visible\n label — place it inside `FormField.Root` with `FormField.Label`, or pass an\n `aria-label` (or `aria-labelledby`) when there's no surrounding form field.\n This names the group; `thumbLabels` names the individual handles.\n- **Choose a scale people can reason about.** Range sliders work best when the\n exact numbers matter less than their position relative to the whole range —\n pick `minValue`, `maxValue`, and `step` so most handle positions feel\n meaningful.\n- **Summarize the selected range with `FormField.Description`, not a duplicate\n readout.** Each handle's value already appears in its tooltip on hover, focus,\n or drag. A `FormField.Description` that states the current bounds (e.g. a price\n range) is a separate, permanent piece of context and is fine to keep — don't\n add a second number readout right beside the track that just repeats the\n tooltip.\n- **Reach for `RangeSlider` when both bounds are set together by feel.** Its two\n handles can't cross, so it guarantees the lower value never exceeds the upper\n one — useful for filters like price, distance, or rating ranges.\n- **Disable, don't hide.** When the range is temporarily not applicable, disable\n it with `isDisabled` rather than removing it from the layout, so surrounding\n controls don't shift.\n\n### Usage\n\nA range slider lets users choose a lower and an upper bound by dragging two\nhandles along a shared track, best for ranges that are set by feel rather than\ntyped precisely.\n\n> [!TIP]\\\n> When to use\n>\n> - Filtering by a numeric range set by feel, such as price, distance, or\n> rating.\n> - Both a lower and an upper bound need to be chosen together, and the pair\n> must stay valid (lower ≤ upper).\n> - The exact numbers matter less than their position relative to the minimum\n> and maximum.\n\n> [!CAUTION]\\\n> When not to use\n>\n> - Only a single value is needed — use [`Slider`](/components/inputs/slider)\n> instead.\n> - The bounds must be typed precisely, or fall outside a small, well-known\n> range — pair two `NumberInput`s instead.\n> - Users need to see the min/max bounds labeled directly on the track — that\n> isn't supported yet; consider pairing the slider with your own labels.\n\n### Keeping interactions simple\n\nSimple, predictable interactions matter because a range slider is operated by\nfeel — unexpected jumps or hidden values erode trust in the control.\n\n> [!TIP]\\\n> **Do**\n>\n> - Give the control a clear accessible name via `FormField.Label` or\n> `aria-label`, and name each handle with `thumbLabels`.\n> - Set `minValue`, `maxValue`, and a `step` that make sense for the data.\n> - Summarize the current bounds in a `FormField.Description` when a permanent\n> readout adds context.\n> - Let the disabled state show when the range can't currently be changed.\n\n```jsx live\nconst App = () => {\n const [range, setRange] = useState([20, 80]);\n\n return (\n <Stack gap=\"200\" alignItems=\"start\">\n <FormField.Root>\n <FormField.Label>Price range</FormField.Label>\n <FormField.Input>\n <RangeSlider\n thumbLabels={[\"Minimum price\", \"Maximum price\"]}\n value={range}\n onChange={setRange}\n minValue={0}\n maxValue={200}\n formatOptions={{ style: \"currency\", currency: \"USD\" }}\n />\n </FormField.Input>\n <FormField.Description>\n ${range[0]} – ${range[1]}\n </FormField.Description>\n </FormField.Root>\n </Stack>\n );\n};\n```\n\n> [!CAUTION]\\\n> **Don't**\n>\n> - Leave the control without any accessible name — a bare `<RangeSlider />`\n> with no `aria-label` and no `FormField.Label` is unusable with a screen\n> reader.\n> - Duplicate the tooltip's job by placing a bare numeric readout directly\n> beside the track — the tooltips already cover that on hover, focus, and\n> drag.\n> - Use a range slider for bounds that require exact, typed precision.\n\n```tsx\n// Missing an accessible name — screen reader users have no way to know what\n// this control is for, and the handles are unnamed.\n<RangeSlider defaultValue={[20, 60]} minValue={0} maxValue={100} />\n```\n",
586
+ "toc": [
587
+ {
588
+ "value": "Guidelines",
589
+ "href": "#guidelines",
590
+ "depth": 2,
591
+ "numbering": [
592
+ 1,
593
+ 1
594
+ ],
595
+ "parent": "root"
596
+ },
597
+ {
598
+ "value": "Best practices",
599
+ "href": "#best-practices",
600
+ "depth": 3,
601
+ "numbering": [
602
+ 1,
603
+ 1,
604
+ 1
605
+ ],
606
+ "parent": "root"
607
+ },
608
+ {
609
+ "value": "Usage",
610
+ "href": "#usage",
611
+ "depth": 3,
612
+ "numbering": [
613
+ 1,
614
+ 1,
615
+ 2
616
+ ],
617
+ "parent": "root"
618
+ },
619
+ {
620
+ "value": "Keeping interactions simple",
621
+ "href": "#keeping-interactions-simple",
622
+ "depth": 3,
623
+ "numbering": [
624
+ 1,
625
+ 1,
626
+ 3
627
+ ],
628
+ "parent": "root"
629
+ }
630
+ ]
631
+ }
632
+ }
633
+ }