@nexus-cross/design-system 1.0.14 → 1.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 (282) hide show
  1. package/claude-rules/nexus/CLAUDE.md +85 -0
  2. package/claude-rules/nexus/commands/nexus-audit.md +79 -0
  3. package/claude-rules/nexus/commands/nexus-component-map.md +85 -0
  4. package/claude-rules/nexus/commands/nexus-token-check.md +68 -0
  5. package/claude-rules/nexus/skills/nexus-design-system/SKILL.md +92 -0
  6. package/cursor-rules/nexus-ui-api.mdc +824 -41
  7. package/cursor-rules/nexus-ui-decisions.mdc +259 -0
  8. package/dist/accordion.js +0 -1
  9. package/dist/accordion.mjs +0 -1
  10. package/dist/alert.js +0 -1
  11. package/dist/alert.mjs +0 -1
  12. package/dist/avatar.js +0 -1
  13. package/dist/avatar.mjs +0 -1
  14. package/dist/badge.js +0 -1
  15. package/dist/badge.mjs +0 -1
  16. package/dist/breadcrumb.js +0 -1
  17. package/dist/breadcrumb.mjs +0 -1
  18. package/dist/button.js +0 -1
  19. package/dist/button.mjs +0 -1
  20. package/dist/carousel.js +0 -1
  21. package/dist/carousel.mjs +0 -1
  22. package/dist/checkbox.js +0 -1
  23. package/dist/checkbox.mjs +0 -1
  24. package/dist/chip.js +0 -1
  25. package/dist/chip.mjs +0 -1
  26. package/dist/chunks/chunk-2Z52NPWB.js +78 -0
  27. package/dist/chunks/chunk-46P52MFM.mjs +56 -0
  28. package/dist/chunks/chunk-56ZOOQFE.mjs +514 -0
  29. package/dist/chunks/chunk-5ASTWFJW.js +538 -0
  30. package/dist/chunks/{chunk-33UFQJIO.mjs → chunk-BJMXZJWO.mjs} +16 -5
  31. package/dist/chunks/chunk-EILXBLEV.mjs +5 -0
  32. package/dist/chunks/chunk-G3RLK2HS.js +7 -0
  33. package/dist/chunks/{chunk-YZV6FWE7.js → chunk-JLDQNDFT.js} +16 -5
  34. package/dist/chunks/{chunk-K574BYHQ.js → chunk-K3CK7NTP.js} +22 -4
  35. package/dist/chunks/{chunk-Z4YM7LU3.mjs → chunk-PIGHBDK5.mjs} +22 -4
  36. package/dist/chunks/{chunk-PEIEVKD5.js → chunk-RCIBLLSF.js} +11 -12
  37. package/dist/chunks/{chunk-K2TBLM3F.mjs → chunk-THBE27U3.mjs} +11 -12
  38. package/dist/client-only.js +0 -1
  39. package/dist/client-only.mjs +0 -1
  40. package/dist/combobox.js +16 -0
  41. package/dist/combobox.mjs +3 -0
  42. package/dist/components/Combobox.d.ts +48 -0
  43. package/dist/components/Combobox.d.ts.map +1 -0
  44. package/dist/components/DataGrid.d.ts +44 -0
  45. package/dist/components/DataGrid.d.ts.map +1 -0
  46. package/dist/components/DataList.d.ts +3 -1
  47. package/dist/components/DataList.d.ts.map +1 -1
  48. package/dist/components/RadioGroup.d.ts +4 -0
  49. package/dist/components/RadioGroup.d.ts.map +1 -1
  50. package/dist/components/ToggleGroup.d.ts +2 -1
  51. package/dist/components/ToggleGroup.d.ts.map +1 -1
  52. package/dist/countdown.js +0 -1
  53. package/dist/countdown.mjs +0 -1
  54. package/dist/counter.js +0 -1
  55. package/dist/counter.mjs +0 -1
  56. package/dist/data-grid.js +14 -0
  57. package/dist/data-grid.mjs +5 -0
  58. package/dist/data-list.js +2 -3
  59. package/dist/data-list.mjs +1 -2
  60. package/dist/date-picker.js +0 -1
  61. package/dist/date-picker.mjs +0 -1
  62. package/dist/divider.js +0 -1
  63. package/dist/divider.mjs +0 -1
  64. package/dist/drawer.js +0 -1
  65. package/dist/drawer.mjs +0 -1
  66. package/dist/dropdown-menu.js +0 -1
  67. package/dist/dropdown-menu.mjs +0 -1
  68. package/dist/ellipsis.js +0 -1
  69. package/dist/ellipsis.mjs +0 -1
  70. package/dist/empty-state.js +0 -1
  71. package/dist/empty-state.mjs +0 -1
  72. package/dist/error-boundary.js +0 -1
  73. package/dist/error-boundary.mjs +0 -1
  74. package/dist/hooks/useCheckDevice.js +0 -1
  75. package/dist/hooks/useCheckDevice.mjs +0 -1
  76. package/dist/hooks/useClickOutside.js +0 -1
  77. package/dist/hooks/useClickOutside.mjs +0 -1
  78. package/dist/hooks/useDraggableBottomSheet.js +0 -1
  79. package/dist/hooks/useDraggableBottomSheet.mjs +0 -1
  80. package/dist/hooks/useDraggableWindow.js +0 -1
  81. package/dist/hooks/useDraggableWindow.mjs +0 -1
  82. package/dist/hooks/useInView.js +0 -1
  83. package/dist/hooks/useInView.mjs +0 -1
  84. package/dist/hooks/useModal.js +0 -1
  85. package/dist/hooks/useModal.mjs +0 -1
  86. package/dist/image-upload.js +0 -1
  87. package/dist/image-upload.mjs +0 -1
  88. package/dist/index.d.ts +4 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +89 -85
  91. package/dist/index.mjs +11 -11
  92. package/dist/infinite-scroll.js +0 -1
  93. package/dist/infinite-scroll.mjs +0 -1
  94. package/dist/marquee.js +0 -1
  95. package/dist/marquee.mjs +0 -1
  96. package/dist/modal/index.js +11 -12
  97. package/dist/modal/index.mjs +2 -3
  98. package/dist/number-input.js +0 -1
  99. package/dist/number-input.mjs +0 -1
  100. package/dist/nx-image.js +0 -1
  101. package/dist/nx-image.mjs +0 -1
  102. package/dist/pagination.js +0 -1
  103. package/dist/pagination.mjs +0 -1
  104. package/dist/popover.js +0 -1
  105. package/dist/popover.mjs +0 -1
  106. package/dist/price-input.js +0 -1
  107. package/dist/price-input.mjs +0 -1
  108. package/dist/progress.js +0 -1
  109. package/dist/progress.mjs +0 -1
  110. package/dist/radio-group.js +5 -6
  111. package/dist/radio-group.mjs +1 -2
  112. package/dist/schemas/_all.json +308 -117
  113. package/dist/schemas/accordion.d.ts.map +1 -1
  114. package/dist/schemas/accordion.json +1 -1
  115. package/dist/schemas/alert.d.ts.map +1 -1
  116. package/dist/schemas/alert.json +1 -1
  117. package/dist/schemas/avatar.d.ts.map +1 -1
  118. package/dist/schemas/avatar.json +1 -1
  119. package/dist/schemas/badge.d.ts.map +1 -1
  120. package/dist/schemas/badge.json +1 -1
  121. package/dist/schemas/breadcrumb.d.ts.map +1 -1
  122. package/dist/schemas/breadcrumb.json +1 -1
  123. package/dist/schemas/button.d.ts.map +1 -1
  124. package/dist/schemas/button.json +1 -1
  125. package/dist/schemas/carousel.d.ts.map +1 -1
  126. package/dist/schemas/carousel.json +1 -1
  127. package/dist/schemas/checkBox.json +1 -1
  128. package/dist/schemas/checkbox.d.ts.map +1 -1
  129. package/dist/schemas/chip.d.ts.map +1 -1
  130. package/dist/schemas/chip.json +1 -1
  131. package/dist/schemas/client-only.d.ts.map +1 -1
  132. package/dist/schemas/clientOnly.json +1 -1
  133. package/dist/schemas/combobox.d.ts +85 -0
  134. package/dist/schemas/combobox.d.ts.map +1 -0
  135. package/dist/schemas/combobox.json +98 -0
  136. package/dist/schemas/comboboxOption.json +30 -0
  137. package/dist/schemas/countdown.d.ts.map +1 -1
  138. package/dist/schemas/countdown.json +1 -1
  139. package/dist/schemas/counter.d.ts.map +1 -1
  140. package/dist/schemas/counter.json +1 -1
  141. package/dist/schemas/data-grid.d.ts +74 -0
  142. package/dist/schemas/data-grid.d.ts.map +1 -0
  143. package/dist/schemas/data-list.d.ts.map +1 -1
  144. package/dist/schemas/dataGrid.json +102 -0
  145. package/dist/schemas/dataList.json +1 -1
  146. package/dist/schemas/date-picker.d.ts.map +1 -1
  147. package/dist/schemas/datePicker.json +1 -1
  148. package/dist/schemas/divider.d.ts.map +1 -1
  149. package/dist/schemas/divider.json +1 -1
  150. package/dist/schemas/drawer.d.ts.map +1 -1
  151. package/dist/schemas/drawer.json +1 -1
  152. package/dist/schemas/dropdown-menu.d.ts.map +1 -1
  153. package/dist/schemas/dropdownMenu.json +1 -1
  154. package/dist/schemas/ellipsis.d.ts.map +1 -1
  155. package/dist/schemas/ellipsis.json +1 -1
  156. package/dist/schemas/empty-state.d.ts.map +1 -1
  157. package/dist/schemas/emptyState.json +1 -1
  158. package/dist/schemas/error-boundary.d.ts.map +1 -1
  159. package/dist/schemas/errorBoundary.json +1 -1
  160. package/dist/schemas/image-upload.d.ts.map +1 -1
  161. package/dist/schemas/imageUpload.json +1 -1
  162. package/dist/schemas/index.d.ts +2 -1
  163. package/dist/schemas/index.d.ts.map +1 -1
  164. package/dist/schemas/infinite-scroll.d.ts.map +1 -1
  165. package/dist/schemas/infiniteScroll.json +1 -1
  166. package/dist/schemas/marquee.d.ts.map +1 -1
  167. package/dist/schemas/marquee.json +1 -1
  168. package/dist/schemas/modal.d.ts.map +1 -1
  169. package/dist/schemas/modalTemplate.json +1 -1
  170. package/dist/schemas/number-input.d.ts.map +1 -1
  171. package/dist/schemas/numberInput.json +1 -1
  172. package/dist/schemas/nx-image.d.ts.map +1 -1
  173. package/dist/schemas/nxImage.json +1 -1
  174. package/dist/schemas/pagination.d.ts.map +1 -1
  175. package/dist/schemas/pagination.json +1 -1
  176. package/dist/schemas/popover.d.ts.map +1 -1
  177. package/dist/schemas/popover.json +1 -1
  178. package/dist/schemas/price-input.d.ts.map +1 -1
  179. package/dist/schemas/priceInput.json +1 -1
  180. package/dist/schemas/progress.d.ts.map +1 -1
  181. package/dist/schemas/progress.json +1 -1
  182. package/dist/schemas/radio-group.d.ts +9 -0
  183. package/dist/schemas/radio-group.d.ts.map +1 -1
  184. package/dist/schemas/radioGroup.json +10 -1
  185. package/dist/schemas/radioItem.json +11 -0
  186. package/dist/schemas/select.d.ts.map +1 -1
  187. package/dist/schemas/select.json +1 -1
  188. package/dist/schemas/skeleton.d.ts.map +1 -1
  189. package/dist/schemas/skeleton.json +1 -1
  190. package/dist/schemas/slider.d.ts.map +1 -1
  191. package/dist/schemas/slider.json +1 -1
  192. package/dist/schemas/spinner.d.ts.map +1 -1
  193. package/dist/schemas/spinner.json +1 -1
  194. package/dist/schemas/stepper.d.ts.map +1 -1
  195. package/dist/schemas/stepper.json +1 -1
  196. package/dist/schemas/switch.d.ts.map +1 -1
  197. package/dist/schemas/switch.json +1 -1
  198. package/dist/schemas/tab.d.ts.map +1 -1
  199. package/dist/schemas/tab.json +1 -1
  200. package/dist/schemas/table.d.ts.map +1 -1
  201. package/dist/schemas/table.json +1 -1
  202. package/dist/schemas/tableRow.json +1 -1
  203. package/dist/schemas/tag-input.d.ts.map +1 -1
  204. package/dist/schemas/tagInput.json +1 -1
  205. package/dist/schemas/tdColumn.json +1 -1
  206. package/dist/schemas/text-area.d.ts.map +1 -1
  207. package/dist/schemas/text-input.d.ts +2 -2
  208. package/dist/schemas/text-input.d.ts.map +1 -1
  209. package/dist/schemas/textArea.json +1 -1
  210. package/dist/schemas/textInput.json +1 -1
  211. package/dist/schemas/toast.d.ts.map +1 -1
  212. package/dist/schemas/toastOptions.json +1 -1
  213. package/dist/schemas/toaster.json +1 -1
  214. package/dist/schemas/toggle-group.d.ts +6 -3
  215. package/dist/schemas/toggle-group.d.ts.map +1 -1
  216. package/dist/schemas/toggleGroup.json +9 -3
  217. package/dist/schemas/tooltip.d.ts.map +1 -1
  218. package/dist/schemas/tooltip.json +1 -1
  219. package/dist/schemas/virtual-scroll.d.ts.map +1 -1
  220. package/dist/schemas/virtualGrid.json +1 -1
  221. package/dist/schemas/virtualList.json +1 -1
  222. package/dist/schemas.js +867 -66
  223. package/dist/schemas.mjs +865 -66
  224. package/dist/select.js +0 -1
  225. package/dist/select.mjs +0 -1
  226. package/dist/skeleton.js +0 -1
  227. package/dist/skeleton.mjs +0 -1
  228. package/dist/slider.js +0 -1
  229. package/dist/slider.mjs +0 -1
  230. package/dist/spinner.js +0 -1
  231. package/dist/spinner.mjs +0 -1
  232. package/dist/stepper.js +0 -1
  233. package/dist/stepper.mjs +0 -1
  234. package/dist/styles/.generated/built.d.ts +1 -1
  235. package/dist/styles/.generated/built.d.ts.map +1 -1
  236. package/dist/styles/layer.js +2 -3
  237. package/dist/styles/layer.mjs +1 -2
  238. package/dist/styles.css +471 -13
  239. package/dist/styles.js +2 -3
  240. package/dist/styles.layered.css +471 -13
  241. package/dist/styles.mjs +1 -2
  242. package/dist/switch.js +0 -1
  243. package/dist/switch.mjs +0 -1
  244. package/dist/tab.js +0 -1
  245. package/dist/tab.mjs +0 -1
  246. package/dist/table.js +0 -1
  247. package/dist/table.mjs +0 -1
  248. package/dist/tag-input.js +0 -1
  249. package/dist/tag-input.mjs +0 -1
  250. package/dist/text-area.js +0 -1
  251. package/dist/text-area.mjs +0 -1
  252. package/dist/text-input.js +0 -1
  253. package/dist/text-input.mjs +0 -1
  254. package/dist/toast.js +0 -1
  255. package/dist/toast.mjs +0 -1
  256. package/dist/toggle-group.js +3 -4
  257. package/dist/toggle-group.mjs +1 -2
  258. package/dist/tooltip.js +0 -1
  259. package/dist/tooltip.mjs +0 -1
  260. package/dist/utils/cn.js +0 -1
  261. package/dist/utils/cn.mjs +0 -1
  262. package/dist/utils/scroll.js +0 -1
  263. package/dist/utils/scroll.mjs +0 -1
  264. package/dist/virtual-scroll.js +0 -1
  265. package/dist/virtual-scroll.mjs +0 -1
  266. package/package.json +14 -8
  267. package/scripts/setup-cursor-rules.cjs +164 -27
  268. package/dist/chunks/chunk-22ULI3BF.js +0 -21
  269. package/dist/chunks/chunk-CVYXRSXT.mjs +0 -8
  270. package/dist/chunks/chunk-I252NERB.mjs +0 -21
  271. package/dist/chunks/chunk-JNMCYWGY.js +0 -10
  272. package/dist/chunks/chunk-LAOQRXCE.js +0 -7
  273. package/dist/chunks/chunk-S6ODYMFP.mjs +0 -5
  274. package/dist/components/ThemeProvider.d.ts +0 -25
  275. package/dist/components/ThemeProvider.d.ts.map +0 -1
  276. package/dist/schemas/theme-provider.d.ts +0 -36
  277. package/dist/schemas/theme-provider.d.ts.map +0 -1
  278. package/dist/schemas/themeProvider.json +0 -65
  279. package/dist/theme-provider.js +0 -15
  280. package/dist/theme-provider.mjs +0 -2
  281. package/dist/chunks/{chunk-CWMLTXOH.mjs → chunk-5ZVPTIL3.mjs} +1 -1
  282. package/dist/chunks/{chunk-HFBTS42N.js → chunk-7F4SOLAC.js} +1 -1
@@ -12,7 +12,17 @@ All components are imported from `@nexus-cross/design-system`.
12
12
 
13
13
  ## Button
14
14
 
15
- Interactive button. semantic(color) x variant(style) 2-axis system. Rendering element changeable via asChild.
15
+ Interactive button (always prefer over native <button>).
16
+
17
+ WHEN TO USE: any clickable action — submit, navigate (with asChild), open modal, trigger menu.
18
+ 2-axis: semantic (color intent) × variant (visual weight). Use semantic="primary" for the page's main CTA, "danger" for destructive actions, "secondary" for sub actions, "normal" for neutral.
19
+
20
+ ANTI-PATTERNS:
21
+ ✗ <button className="bg-blue-500"> → <Button semantic="primary">
22
+ ✗ <a className="..."> styled as button → <Button asChild><a href="..."/></Button>
23
+ ✗ Mixing variants randomly within one row — pick one per visual hierarchy level
24
+ ✗ Using primary + contained for every button → only ONE primary CTA per view
25
+ ✗ <Button className="!bg-red-500"> → <Button semantic="danger"> (no !important)
16
26
 
17
27
  | Prop | Type | Default | Description |
18
28
  |---|---|---|---|
@@ -59,7 +69,18 @@ Interactive button. semantic(color) x variant(style) 2-axis system. Rendering el
59
69
 
60
70
  ## TextInput
61
71
 
62
- Text input field. Supports label, description, prefix/suffix icons, clearable, character counter.
72
+ Single-line text input (always prefer over native <input type="text">).
73
+
74
+ WHEN TO USE: any short text — name, email, search, URL, password.
75
+ Use the built-in label/description props instead of wrapping with your own <label>/<p> tags (auto-binds htmlFor/aria-describedby for accessibility).
76
+ For numeric input use NumberInput; for currency PriceInput; for long text TextArea; for date DatePicker.
77
+
78
+ ANTI-PATTERNS:
79
+ ✗ <TextInput type="number"> → <NumberInput> (gives unit, step, ↑↓ keys)
80
+ ✗ <label>Email <input ...></label> + <p>helper</p> → <TextInput label="Email" description="helper">
81
+ ✗ Custom red border for error → use error prop (auto aria-invalid + token color)
82
+ ✗ Manual character counter → use showCount + maxLength
83
+ ✗ Manual clear X button → use clearable prop
63
84
 
64
85
  | Prop | Type | Default | Description |
65
86
  |---|---|---|---|
@@ -119,7 +140,19 @@ Text input field. Supports label, description, prefix/suffix icons, clearable, c
119
140
 
120
141
  ## TextArea
121
142
 
122
- Multi-line text input with label, description, character counter, and resize modes.
143
+ Multi-line text input. Always prefer over native <textarea>.
144
+
145
+ WHEN TO USE:
146
+ • Long-form text — comments, descriptions, memos, bios
147
+ • Single-line input → TextInput
148
+ • Numeric → NumberInput
149
+
150
+ resize="auto" auto-grows with content (great for chat input). resize="none" locks size for fixed UI.
151
+
152
+ ANTI-PATTERNS:
153
+ ✗ Native <textarea> + custom counter → showCount + maxLength
154
+ ✗ <TextArea> for short labels — use TextInput (smaller affordance)
155
+ ✗ Wrapping with custom <label>/<p> → use built-in label/description props (auto a11y binding)
123
156
 
124
157
  | Prop | Type | Default | Description |
125
158
  |---|---|---|---|
@@ -158,7 +191,19 @@ Multi-line text input with label, description, character counter, and resize mod
158
191
 
159
192
  ## Select
160
193
 
161
- Dropdown select. Based on Radix Select. Used with SelectItem.
194
+ Dropdown select for short option lists. Based on Radix Select. Used with SelectItem.
195
+
196
+ WHEN TO USE:
197
+ • Options ≤ 7, no search needed → Select
198
+ • Options ≥ 7 OR search needed OR async → Combobox instead
199
+ • Need multi-select → Combobox (multiple)
200
+ • Action menu (save/delete/share) → DropdownMenu (not Select; values vs actions)
201
+
202
+ ANTI-PATTERNS:
203
+ ✗ Select with 20+ options → Combobox
204
+ ✗ Using Select for menu items that trigger functions → DropdownMenu
205
+ ✗ Manual <select> styling → Select gives consistent token styling
206
+ ✗ Wrapping each SelectItem with Tooltip — instead put hint in item label
162
207
 
163
208
  | Prop | Type | Default | Description |
164
209
  |---|---|---|---|
@@ -194,9 +239,153 @@ Individual option within Select.
194
239
 
195
240
  ---
196
241
 
242
+ ## Combobox
243
+
244
+ Searchable select. Text input + popover listbox. Single/multi-select. Sync (auto-filter) or async (onSearch + loading) modes.
245
+
246
+ WHEN TO USE:
247
+ • Options ≥ 7, OR labels are long, OR search/filter is needed → Combobox (not Select)
248
+ • Multi-select form field → Combobox with multiple (chips render inside input)
249
+ • Async data from server → set onSearch + loading
250
+ For ≤7 simple options use Select. For free-text tags use TagInput.
251
+
252
+ ASYNC PATTERN:
253
+ <Combobox options={results} loading={isFetching} onSearch={(q) => mutate(q)} />
254
+ — onSearch fires after searchDebounce (default 250ms). Do NOT clear input on result update; component preserves user's typing.
255
+
256
+ IME (Korean/Japanese/Chinese): Enter during composition is ignored automatically — do not add custom keydown handlers.
257
+
258
+ ANTI-PATTERNS:
259
+ ✗ <Select> with 20 options → <Combobox>
260
+ ✗ Manual <input> + dropdown div + filter logic → <Combobox>
261
+ ✗ Setting value externally to clear input mid-typing → use onValueChange instead
262
+
263
+ | Prop | Type | Default | Description |
264
+ |---|---|---|---|
265
+ | `options` | `ReactNode` | - | Available options array (ComboboxOption[], required) |
266
+ | `value` | `ReactNode` | - | Selected value. string for single, string[] for multiple |
267
+ | `defaultValue` | `ReactNode` | - | Initial value (uncontrolled) |
268
+ | `onValueChange` | `ReactNode` | - | Value change callback. (value: string | string[]) => void |
269
+ | `multiple` | `boolean` | `false` | Multi-select mode. Selected values shown as chips inside input |
270
+ | `onSearch` | `ReactNode` | - | Async search callback. (query: string) => void. Triggers external data fetching with debounce |
271
+ | `searchDebounce` | `number` | `250` | Debounce delay (ms) before onSearch fires |
272
+ | `loading` | `boolean` | `false` | Externally-controlled loading state. Shows spinner in input suffix |
273
+ | `filter` | `ReactNode` | - | Custom client-side filter. (option, query) => boolean. Default: case-insensitive label includes match |
274
+ | `placeholder` | `string` | - | Input placeholder |
275
+ | `emptyMessage` | `ReactNode` | - | Message when no options match (string | ReactNode). Default: "검색 결과 없음" |
276
+ | `loadingMessage` | `ReactNode` | - | Message during loading state inside popover (string | ReactNode). Default: "검색 중…" |
277
+ | `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Input size (matches TextInput tokens) |
278
+ | `disabled` | `boolean` | - | Disabled |
279
+ | `error` | `boolean` | - | Error state |
280
+ | `clearable` | `boolean` | `true` | Show clear button when value(s) exist |
281
+ | `autoOpenOnFocus` | `boolean` | `true` | Open popover automatically when input gains focus |
282
+ | `label` | `ReactNode` | - | Field label (ReactNode) |
283
+ | `description` | `ReactNode` | - | Helper text below input (ReactNode) |
284
+ | `className` | `string` | - | Wrapper className |
285
+ | `popoverClassName` | `string` | - | Popover content className |
286
+
287
+ ### ComboboxOption
288
+
289
+ Single Combobox option.
290
+
291
+ | Prop | Type | Default | Description |
292
+ |---|---|---|---|
293
+ | `value` | `string` | - | Option value (unique key) |
294
+ | `label` | `ReactNode` | - | Display label (string | ReactNode) |
295
+ | `description` | `ReactNode` | - | Secondary text below label (ReactNode) |
296
+ | `disabled` | `boolean` | - | Disabled option |
297
+
298
+ Searchable select with popover listbox. Supports single/multi-select and sync (auto-filter) / async (onSearch + loading) modes. **WAI-ARIA Combobox pattern** (Radix Popover under the hood — NOT Radix Select).
299
+
300
+ - **Sync mode** (no `onSearch` prop): client-side filters `options` by typed query (case-insensitive label includes match by default; override via `filter`).
301
+ - **Async mode** (provide `onSearch`): debounced (`searchDebounce`, default 250ms) callback fires for external data fetch. Set `loading` to show spinner in the input suffix.
302
+ - **Multi-select** (`multiple`): selected values render as removable chips inside the input. Backspace on empty input removes the last chip.
303
+ - Keyboard: Arrow Up/Down, Home/End, Enter to select, Escape to close.
304
+
305
+ ```tsx
306
+ // 1) Sync — auto-filter local options
307
+ const COUNTRIES = [
308
+ { value: 'kr', label: '한국' },
309
+ { value: 'jp', label: '일본' },
310
+ { value: 'us', label: '미국' },
311
+ ];
312
+
313
+ <Combobox
314
+ options={COUNTRIES}
315
+ value={value}
316
+ onValueChange={setValue}
317
+ placeholder="국가 선택"
318
+ />
319
+
320
+ // 2) Async — external search w/ loading spinner
321
+ const [results, setResults] = useState<ComboboxOption[]>([]);
322
+ const [loading, setLoading] = useState(false);
323
+
324
+ const handleSearch = async (q: string) => {
325
+ if (!q) return setResults([]);
326
+ setLoading(true);
327
+ const users = await fetchUsers(q);
328
+ setResults(users.map((u) => ({ value: u.id, label: u.name, description: u.email })));
329
+ setLoading(false);
330
+ };
331
+
332
+ <Combobox
333
+ options={results}
334
+ loading={loading}
335
+ onSearch={handleSearch}
336
+ value={selectedId}
337
+ onValueChange={setSelectedId}
338
+ placeholder="유저 검색…"
339
+ emptyMessage="검색 결과 없음"
340
+ />
341
+
342
+ // 3) Multi-select with chips
343
+ <Combobox
344
+ multiple
345
+ options={results}
346
+ value={selectedIds}
347
+ onValueChange={setSelectedIds}
348
+ loading={loading}
349
+ onSearch={handleSearch}
350
+ />
351
+
352
+ // 4) With label / description / error
353
+ <Combobox
354
+ label="담당자"
355
+ description="검색 후 선택하세요"
356
+ options={results}
357
+ loading={loading}
358
+ onSearch={handleSearch}
359
+ value={value}
360
+ onValueChange={setValue}
361
+ error={!value}
362
+ size="lg"
363
+ />
364
+ ```
365
+
366
+ **When to use `Select` vs `Combobox`**:
367
+ - Fixed short list, no search needed → `Select`
368
+ - Long list / async search / typed input → `Combobox`
369
+ - Free-form tag input (any string) → `TagInput`
370
+
371
+ ---
372
+
197
373
  ## CheckBox
198
374
 
199
- Checkbox. Native input-based, supports square/round shapes.
375
+ Checkbox for multi-select form fields. Native input-based, supports square/round shapes.
376
+
377
+ WHEN TO USE:
378
+ • Form field with multiple independent options (T&C agreements, multi-select filters submitted later)
379
+ • Tri-state (parent-child selection) — use indeterminate prop
380
+ • Single binary that takes effect immediately → Switch instead
381
+ • Many options with search → Combobox (multiple)
382
+
383
+ INDETERMINATE: set indeterminate=true when some children are checked, others not. aria-checked becomes "mixed" automatically.
384
+
385
+ ANTI-PATTERNS:
386
+ ✗ <CheckBox> for "Enable dark mode" toggle → <Switch>
387
+ ✗ Native <input type="checkbox"> + manual styling → <CheckBox>
388
+ ✗ Forgetting indeterminate for "select all" parent
200
389
 
201
390
  | Prop | Type | Default | Description |
202
391
  |---|---|---|---|
@@ -229,7 +418,24 @@ Checkbox. Native input-based, supports square/round shapes.
229
418
 
230
419
  ## RadioGroup
231
420
 
232
- Radio group. Used with RadioItem.
421
+ Radio group for single-choice form fields. Used with RadioItem.
422
+
423
+ WHEN TO USE:
424
+ • Form field where user picks ONE of small set (≤4-7) and submits later → RadioGroup
425
+ • All options should be visible at once for comparison → RadioGroup (not Select)
426
+ • Immediate effect on selection (no submit) → ToggleGroup instead
427
+ • Page area switching (tabs) → Tab (not RadioGroup)
428
+ • Many options → Select / Combobox
429
+
430
+ VARIANTS:
431
+ • variant="default" — outline circle with inner dot when checked (classic)
432
+ • variant="ring" — thick border replaces inner dot when checked (modern, Figma "ring" style)
433
+
434
+ ANTI-PATTERNS:
435
+ ✗ Using RadioGroup for tab-like content switching → Tab
436
+ ✗ Using RadioGroup for immediate filters → ToggleGroup
437
+ ✗ Mixing RadioGroup and ToggleGroup in same form → pick one pattern
438
+ ✗ Native <input type="radio"> → RadioItem (gets a11y, focus ring, tokens)
233
439
 
234
440
  | Prop | Type | Default | Description |
235
441
  |---|---|---|---|
@@ -237,6 +443,7 @@ Radio group. Used with RadioItem.
237
443
  | `value` | `string` | - | Selected value (controlled) |
238
444
  | `defaultValue` | `string` | - | Initial value (uncontrolled) |
239
445
  | `size` | `'sm'` \| `'md'` | `"md"` | Size |
446
+ | `variant` | `'default'` \| `'ring'` | `"default"` | Visual style. default=outline circle with inner dot, ring=thick border replaces dot when checked |
240
447
  | `orientation` | `'horizontal'` \| `'vertical'` | `"vertical"` | Layout direction |
241
448
  | `disabled` | `boolean` | - | Disabled |
242
449
  | `children` | `ReactNode` | - | RadioItem list (ReactNode, required) |
@@ -253,7 +460,9 @@ Individual option within RadioGroup.
253
460
  |---|---|---|---|
254
461
  | `value` | `string` | - | Item value (required) |
255
462
  | `size` | `'sm'` \| `'md'` | - | Size (overrides group) |
463
+ | `variant` | `'default'` \| `'ring'` | - | Visual style (overrides group) |
256
464
  | `label` | `ReactNode` | - | Label text (ReactNode) |
465
+ | `description` | `ReactNode` | - | Help text shown beneath the label (ReactNode) |
257
466
  | `children` | `ReactNode` | - | Label alternative content (ReactNode) |
258
467
  | `disabled` | `boolean` | - | Disabled |
259
468
  | `className` | `string` | - | Style override |
@@ -276,7 +485,18 @@ Individual option within RadioGroup.
276
485
 
277
486
  ## Switch
278
487
 
279
- Toggle switch. Native checkbox-based, role="switch".
488
+ Toggle switch for immediate on/off binary state. Native checkbox-based, role="switch".
489
+
490
+ WHEN TO USE:
491
+ • Setting that takes effect immediately (notifications on/off, dark mode)
492
+ • Binary state with no submit step
493
+ • Form field submitted later → CheckBox instead (semantics: checkbox is form data)
494
+ • Multiple related options → CheckBox group or ToggleGroup (multiple)
495
+
496
+ ANTI-PATTERNS:
497
+ ✗ <CheckBox> for "Enable notifications" toggle → <Switch>
498
+ ✗ <Switch> inside form requiring submit → <CheckBox>
499
+ ✗ Wrapping Switch in <label onClick> manually → use label prop or htmlFor pattern
280
500
 
281
501
  | Prop | Type | Default | Description |
282
502
  |---|---|---|---|
@@ -298,7 +518,19 @@ Toggle switch. Native checkbox-based, role="switch".
298
518
 
299
519
  ## Chip
300
520
 
301
- Chip/tag/badge. Close button displayed via onClose prop.
521
+ Chip small interactive token for filters, tags, removable selections, status labels.
522
+
523
+ WHEN TO USE:
524
+ • Filter token / removable selection (set onClose for X button)
525
+ • Tag/category indicator
526
+ • Status indicator with color (variant="accent")
527
+ • Pure count badge → Badge instead
528
+ • Toggleable filter → ToggleGroup or Chip with onClick + active state
529
+
530
+ ANTI-PATTERNS:
531
+ ✗ Building dismiss UI manually with custom div + X icon → use onClose prop
532
+ ✗ Using Chip as a primary CTA → Button
533
+ ✗ Using Chip for hover-only labels → Tooltip
302
534
 
303
535
  | Prop | Type | Default | Description |
304
536
  |---|---|---|---|
@@ -326,7 +558,18 @@ Chip/tag/badge. Close button displayed via onClose prop.
326
558
 
327
559
  ## Spinner
328
560
 
329
- Loading indicator. SVG-based. Built-in role="status".
561
+ Loading indicator (spinner). SVG-based. role="status" + aria-label built-in.
562
+
563
+ WHEN TO USE:
564
+ • Short loads (<1s), inline indicator (button content while submitting)
565
+ • Long loads with known structure → Skeleton (better perceived performance)
566
+ • Quantifiable progress → Progress
567
+ • Page-level loading boundary inside DataList/DataGrid → use their loading prop instead
568
+
569
+ ANTI-PATTERNS:
570
+ ✗ Custom CSS spinning div → Spinner (a11y + tokens)
571
+ ✗ Using Spinner where Skeleton fits (long loads with stable layout)
572
+ ✗ Forgetting aria-label override when meaning differs from "Loading"
330
573
 
331
574
  | Prop | Type | Default | Description |
332
575
  |---|---|---|---|
@@ -348,6 +591,20 @@ Loading indicator. SVG-based. Built-in role="status".
348
591
 
349
592
  Skeleton loading placeholder. Size/shape via className. With children, wraps transparently to maintain actual size.
350
593
 
594
+ WHEN TO USE:
595
+ • Long load with known component shape (cards, lists, profile headers)
596
+ • Reduces perceived wait time when layout is predictable
597
+ • Short loads (<1s) → Spinner
598
+ • Quantifiable progress → Progress
599
+ • DataList/DataGrid loading → set list={null} (uses skeletonElement prop automatically)
600
+
601
+ GOLDEN RULE: Skeleton must match the real component's shape and size. Mismatch causes layout shift.
602
+
603
+ ANTI-PATTERNS:
604
+ ✗ Generic gray rectangle for everything → match real component shape
605
+ ✗ Skeleton for instant loads (causes flash)
606
+ ✗ Many skeletons of different shapes when component is uniform → use a single skeleton in DataList
607
+
351
608
  | Prop | Type | Default | Description |
352
609
  |---|---|---|---|
353
610
  | `as` | `'div'` \| `'span'` | `"div"` | Rendered tag |
@@ -384,7 +641,19 @@ Skeleton loading placeholder. Size/shape via className. With children, wraps tra
384
641
 
385
642
  ## Divider
386
643
 
387
- Divider. Supports horizontal/vertical, solid/dashed/dotted.
644
+ Divider visual separator (horizontal/vertical line).
645
+
646
+ WHEN TO USE:
647
+ • Separating sections within a card / list
648
+ • Vertical separator between inline items (orientation="vertical")
649
+ • Use color prop sparingly — defaults to border-default token
650
+ • For grouping form sections, prefer larger spacing over Divider
651
+ • Tab/Accordion already provide visual separation — don't add Divider
652
+
653
+ ANTI-PATTERNS:
654
+ ✗ Stacking many Dividers — increase spacing instead
655
+ ✗ Using Divider as decorative line with bright color → use Tailwind border utility on container
656
+ ✗ Inline <hr> with custom CSS → Divider (consistent token)
388
657
 
389
658
  | Prop | Type | Default | Description |
390
659
  |---|---|---|---|
@@ -403,7 +672,20 @@ Divider. Supports horizontal/vertical, solid/dashed/dotted.
403
672
 
404
673
  ## Tooltip
405
674
 
406
- Tooltip. Based on Radix Tooltip. Built-in Provider.
675
+ Tooltip — short text hint shown on hover/focus. Based on Radix Tooltip. Provider built-in.
676
+
677
+ WHEN TO USE:
678
+ • Brief explanation of an icon button or truncated label
679
+ • Hover-only, non-interactive content (text only)
680
+ • Need clickable content inside → Popover (not Tooltip)
681
+ • Action menu → DropdownMenu
682
+ • Force user attention → Modal
683
+
684
+ ANTI-PATTERNS:
685
+ ✗ Buttons/links inside content → use Popover (Tooltip not focusable)
686
+ ✗ Long paragraphs in tooltip → consider Popover or Drawer
687
+ ✗ Tooltip on disabled buttons (won't show in some browsers) — wrap in span instead
688
+ ✗ Critical info only in tooltip — duplicate visible elsewhere for mobile/touch
407
689
 
408
690
  | Prop | Type | Default | Description |
409
691
  |---|---|---|---|
@@ -427,7 +709,19 @@ Tooltip. Based on Radix Tooltip. Built-in Provider.
427
709
 
428
710
  ## Popover
429
711
 
430
- Popover. Based on Radix Popover.
712
+ Popover — anchor-positioned panel with interactive content. Based on Radix Popover.
713
+
714
+ WHEN TO USE:
715
+ • Trigger-anchored UI with buttons, inputs, or rich content
716
+ • Filter/option panel near a button (not a full sidebar)
717
+ • Hover-only text hint → Tooltip (lighter)
718
+ • Action menu list → DropdownMenu (gives role=menu, keyboard nav)
719
+ • Decision dialog → Modal
720
+
721
+ ANTI-PATTERNS:
722
+ ✗ Action lists with onClick handlers → DropdownMenu (a11y semantics)
723
+ ✗ Implementing dropdown manually with Popover + custom buttons → DropdownMenu
724
+ ✗ Long forms inside Popover → Drawer or Modal
431
725
 
432
726
  | Prop | Type | Default | Description |
433
727
  |---|---|---|---|
@@ -453,7 +747,21 @@ Popover. Based on Radix Popover.
453
747
 
454
748
  ## Accordion
455
749
 
456
- Accordion. Supports both items array and composable patterns.
750
+ Accordion collapsible content sections (FAQ, settings groups).
751
+
752
+ WHEN TO USE:
753
+ • FAQ, help docs, settings groups
754
+ • Long page where each section is independently scannable
755
+ • Hidden critical info — DO NOT bury what users always need
756
+ • Tab-like content switching → Tab (Accordion is for stacked, not exclusive)
757
+
758
+ type="single" + collapsible=true → all-closed allowed (recommended for FAQ).
759
+ type="multiple" → multiple sections open at once.
760
+
761
+ ANTI-PATTERNS:
762
+ ✗ Hiding the page's primary content inside collapsed Accordion
763
+ ✗ Single-section Accordion (just show the content)
764
+ ✗ Custom toggle div + state — use Accordion (gets a11y, keyboard nav)
457
765
 
458
766
  | Prop | Type | Default | Description |
459
767
  |---|---|---|---|
@@ -484,7 +792,21 @@ Accordion. Supports both items array and composable patterns.
484
792
 
485
793
  ## Drawer
486
794
 
487
- Drawer/bottom sheet. Based on Vaul. Compound component pattern.
795
+ Drawer / bottom sheet. Based on Vaul. Compound component pattern.
796
+
797
+ WHEN TO USE:
798
+ • Side panel for secondary action while user can still see main content (filter panel, item details)
799
+ • Mobile bottom sheet (direction="bottom")
800
+ • Force decision blocking main flow → Modal instead
801
+ • Inline anchor-positioned UI → Popover instead
802
+
803
+ DIRECTION: bottom (default, mobile-friendly), top, left, right.
804
+ dismissible=true (default) allows swipe/outside-click close. Set false for blocking flows.
805
+
806
+ ANTI-PATTERNS:
807
+ ✗ Drawer for confirmation dialogs → Modal (decision-forcing)
808
+ ✗ Always shouldScaleBackground → only enable for true mobile bottom-sheets
809
+ ✗ Forgetting Drawer.Title → required for screen readers (a11y)
488
810
 
489
811
  | Prop | Type | Default | Description |
490
812
  |---|---|---|---|
@@ -530,6 +852,21 @@ Drawer.Content area.
530
852
 
531
853
  Modal template. All modal components must be wrapped with ModalTemplate.
532
854
 
855
+ WHEN TO USE:
856
+ • Force user decision (delete confirm, submit confirm, blocking dialog)
857
+ • Long form/flow that needs full attention
858
+ • Side panel that doesn't block main flow → Drawer instead
859
+ • Inline contextual UI → Popover instead
860
+ • Short hint → Tooltip instead
861
+
862
+ PREFERRED API: use modal() / useModal() imperative API rather than mounting <ModalTemplate> directly. modal() handles stacking, focus return, ESC, and background scroll automatically.
863
+
864
+ ANTI-PATTERNS:
865
+ ✗ Custom <div className="fixed inset-0"> → modal() (loses focus trap, a11y)
866
+ ✗ Direct <ModalTemplate> mount in render tree → wrap with modal()
867
+ ✗ Modal with no title for screen readers → always pass title prop
868
+ ✗ Multiple modals stacking confusingly → use isAlone:true to close prior modals
869
+
533
870
  | Prop | Type | Default | Description |
534
871
  |---|---|---|---|
535
872
  | `title` | `ReactNode` | - | Header title (ReactNode) |
@@ -627,7 +964,22 @@ openModal({
627
964
 
628
965
  ## Tab
629
966
 
630
- Tab navigation. line/pill variants.
967
+ Tab navigation switch between content panels (settings sections, profile views).
968
+
969
+ WHEN TO USE:
970
+ • Page area swap where only one panel is visible at a time
971
+ • Mutually exclusive content with stable section labels
972
+ • Form field selection → RadioGroup (semantics: not navigation)
973
+ • Immediate filter/option toggle → ToggleGroup
974
+ • Stacked collapsible sections → Accordion
975
+
976
+ destroyInactive=true unmounts hidden panels (saves memory but loses state).
977
+
978
+ ANTI-PATTERNS:
979
+ ✗ Tab with 1 item — just render the panel
980
+ ✗ Tab with 8+ items — consider sub-routing or DropdownMenu
981
+ ✗ Using Tab for form value selection → RadioGroup
982
+ ✗ Custom <button> + onClick + state → Tab (a11y, keyboard, focus management)
631
983
 
632
984
  | Prop | Type | Default | Description |
633
985
  |---|---|---|---|
@@ -657,7 +1009,20 @@ Tab navigation. line/pill variants.
657
1009
 
658
1010
  ## Carousel
659
1011
 
660
- Carousel. Based on Embla Carousel. Sub-components: CarouselSlide, CarouselPrev, CarouselNext, CarouselDots.
1012
+ Carousel — horizontal slide gallery. Based on Embla Carousel. Compound: CarouselSlide, CarouselPrev/Next, CarouselDots.
1013
+
1014
+ WHEN TO USE:
1015
+ • Hero banners, image gallery, product showcase
1016
+ • Multi-card horizontal scroll with snap behavior
1017
+ • Vertical scrolling list → Marquee or VirtualList (not Carousel)
1018
+ • Critical content that must always be visible — Carousel hides slides
1019
+
1020
+ opts={{ loop: true, align: 'start' }} for endless loop. Add Embla autoplay plugin for auto-advance.
1021
+
1022
+ ANTI-PATTERNS:
1023
+ ✗ Critical CTA inside non-first slide (users might never scroll)
1024
+ ✗ Auto-advance without pause-on-hover (a11y)
1025
+ ✗ Manual scroll-snap div → Carousel (gets keyboard nav, indicators)
661
1026
 
662
1027
  | Prop | Type | Default | Description |
663
1028
  |---|---|---|---|
@@ -683,7 +1048,19 @@ Sub-components: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
683
1048
 
684
1049
  ## Pagination
685
1050
 
686
- Pagination. Previous/next + page number buttons.
1051
+ Pagination page-by-page navigation for long datasets.
1052
+
1053
+ WHEN TO USE:
1054
+ • User needs to jump to specific pages (search results, blog archives)
1055
+ • Total count is known and stable
1056
+ • Continuous browsing → InfiniteScroll instead
1057
+ • Real-time stream → InfiniteScroll
1058
+ • Both fit → Pagination is more accessible (keyboard, deep-linkable URL)
1059
+
1060
+ ANTI-PATTERNS:
1061
+ ✗ Pagination with totalPages=1 — hide it
1062
+ ✗ Mixing Pagination + InfiniteScroll on same list (confusing)
1063
+ ✗ Custom page button divs → Pagination (a11y, edge cases like ellipsis)
687
1064
 
688
1065
  | Prop | Type | Default | Description |
689
1066
  |---|---|---|---|
@@ -703,7 +1080,20 @@ Pagination. Previous/next + page number buttons.
703
1080
 
704
1081
  ## Avatar
705
1082
 
706
- Avatar. Supports image, fallback text, and children.
1083
+ Avatar user/entity profile image with text fallback.
1084
+
1085
+ WHEN TO USE:
1086
+ • User profiles, comment authors, team member lists, message sender icons
1087
+ • Image fails / not provided → fallback (initials or icon) shown automatically
1088
+ • Need optimized image (Next.js) → pass <Image> via children, omit src
1089
+ • Larger illustration / logo → use NxImage instead
1090
+
1091
+ shape="square" for organization/team logos; "circle" for people (default).
1092
+
1093
+ ANTI-PATTERNS:
1094
+ ✗ <img> + manual fallback handling → Avatar (handles error)
1095
+ ✗ Avatar without alt for screen readers (always set alt or aria-label)
1096
+ ✗ Mixing avatar sizes inconsistently in a list — pick one size
707
1097
 
708
1098
  | Prop | Type | Default | Description |
709
1099
  |---|---|---|---|
@@ -725,7 +1115,20 @@ Avatar. Supports image, fallback text, and children.
725
1115
 
726
1116
  ## Counter
727
1117
 
728
- Number count animation.
1118
+ Counter animated number tick from startValue to endValue.
1119
+
1120
+ WHEN TO USE:
1121
+ • Marketing landing — "10,000+ users" KPI displays
1122
+ • Stat dashboards (animate when value changes)
1123
+ • Live tickers / real-time data — re-render plain text instead (Counter is one-shot animation)
1124
+ • Currency input / editable number → NumberInput / PriceInput
1125
+
1126
+ triggerOnView=true delays animation until element scrolls into view (good for landing pages).
1127
+
1128
+ ANTI-PATTERNS:
1129
+ ✗ Counter for values that update frequently — animation queue conflicts
1130
+ ✗ Long duration on critical numbers (users wait to read)
1131
+ ✗ Counter without separator for large numbers (hard to read)
729
1132
 
730
1133
  | Prop | Type | Default | Description |
731
1134
  |---|---|---|---|
@@ -748,7 +1151,20 @@ Number count animation.
748
1151
 
749
1152
  ## Countdown
750
1153
 
751
- Countdown timer.
1154
+ Countdown — live timer counting down to endTimestamp (Unix ms).
1155
+
1156
+ WHEN TO USE:
1157
+ • Sale ends in / event starts in / token claim window
1158
+ • OTP / verification code expiry
1159
+ • Counting up (since X) → not Countdown; use Counter or custom interval
1160
+ • Persistent server time → pass server-synced endTimestamp (not Date.now offset)
1161
+
1162
+ showDays=false for short timers (<24h) to declutter. Use render prop for fully custom layouts.
1163
+
1164
+ ANTI-PATTERNS:
1165
+ ✗ Countdown without onEnd handler — UI stuck at 0
1166
+ ✗ Countdown across SSR without hydration safety — pass endTimestamp from server
1167
+ ✗ Updating endTimestamp every render — causes jitter
752
1168
 
753
1169
  | Prop | Type | Default | Description |
754
1170
  |---|---|---|---|
@@ -768,7 +1184,20 @@ Countdown timer.
768
1184
 
769
1185
  ## Marquee
770
1186
 
771
- Marquee (scrolling text/elements).
1187
+ Marquee — endless scrolling content (logo strip, promo banner, news ticker).
1188
+
1189
+ WHEN TO USE:
1190
+ • Logo cloud, partner brands strip, social proof
1191
+ • Critical info that must be read → not Marquee (auto-scroll hurts a11y)
1192
+ • Long static list → VirtualList
1193
+ • User-paced gallery → Carousel
1194
+
1195
+ pauseOnHover=true is recommended whenever content is readable text.
1196
+
1197
+ ANTI-PATTERNS:
1198
+ ✗ Marquee with action buttons inside (hard to click)
1199
+ ✗ Fast-speed marquee on important text (unreadable, a11y violation)
1200
+ ✗ Multiple Marquees on same page in different directions (visual chaos)
772
1201
 
773
1202
  | Prop | Type | Default | Description |
774
1203
  |---|---|---|---|
@@ -790,7 +1219,21 @@ Marquee (scrolling text/elements).
790
1219
 
791
1220
  ## VirtualList
792
1221
 
793
- Virtual scroll list. Based on @tanstack/react-virtual.
1222
+ VirtualList performant rendering of huge lists (1000+ items). Based on @tanstack/react-virtual.
1223
+
1224
+ WHEN TO USE:
1225
+ • Large datasets (>200 rows): chat history, transactions, logs, leaderboard
1226
+ • Stable item height (or measurable per-item via estimateSize fn)
1227
+ • Small list (<100 items) → DataList (much simpler API)
1228
+ • Grid layout → VirtualGrid
1229
+ • Server-side pagination → InfiniteScroll wrapping plain list
1230
+
1231
+ estimateSize is critical — wrong values cause scroll jumps. Pair with onEndReached for server pagination.
1232
+
1233
+ ANTI-PATTERNS:
1234
+ ✗ VirtualList for short lists (DataList is simpler and renders faster)
1235
+ ✗ Variable estimateSize for uniform items — use a single number
1236
+ ✗ Putting interactive overlays inside virtual rows (mounted/unmounted on scroll)
794
1237
 
795
1238
  | Prop | Type | Default | Description |
796
1239
  |---|---|---|---|
@@ -806,7 +1249,18 @@ Virtual scroll list. Based on @tanstack/react-virtual.
806
1249
 
807
1250
  ### VirtualGrid
808
1251
 
809
- Virtual scroll grid. Based on @tanstack/react-virtual.
1252
+ VirtualGrid performant grid for huge lists. Based on @tanstack/react-virtual.
1253
+
1254
+ WHEN TO USE:
1255
+ • Image gallery / card grid with 200+ items
1256
+ • Fixed column count (responsive via JS — recompute columns on breakpoint)
1257
+ • Small grid → DataGrid (no virtualization, simpler API)
1258
+ • Single-column list → VirtualList
1259
+
1260
+ ANTI-PATTERNS:
1261
+ ✗ VirtualGrid with <50 items — DataGrid is simpler
1262
+ ✗ Cards with hover-expanding height (breaks virtualization)
1263
+ ✗ Forgetting to recompute columns on resize → visible empty space
810
1264
 
811
1265
  | Prop | Type | Default | Description |
812
1266
  |---|---|---|---|
@@ -842,7 +1296,21 @@ Same as VirtualList + `columns: number` (required).
842
1296
 
843
1297
  ## DataList
844
1298
 
845
- Data list. Automatically handles loading/skeleton/empty/data states based on list. Built-in ErrorBoundary.
1299
+ DataList — render-prop list that handles loading / skeleton / empty / error / data in one component. Built-in ErrorBoundary.
1300
+
1301
+ WHEN TO USE:
1302
+ • Any async list (<200 items) — feed, comments, dashboard rows
1303
+ • Pass list={null} during loading (auto-shows skeleton/spinner)
1304
+ • Pass list=[] for empty state (auto-shows noDataMessage)
1305
+ • Card grid → DataGrid (same API + columns prop)
1306
+ • Huge dataset → VirtualList
1307
+
1308
+ children is render fn: ({ item, index }) => ReactNode.
1309
+
1310
+ ANTI-PATTERNS:
1311
+ ✗ Manual if (loading) {...} else if (!data.length) {...} chains → DataList handles all
1312
+ ✗ DataList without skeletonElement when component shape is known (use Skeleton)
1313
+ ✗ list=undefined (treated as data, not loading) — use null for loading
846
1314
 
847
1315
  | Prop | Type | Default | Description |
848
1316
  |---|---|---|---|
@@ -891,9 +1359,104 @@ function UserSkeleton() {
891
1359
 
892
1360
  ---
893
1361
 
1362
+ ## DataGrid
1363
+
1364
+ DataGrid — card grid version of DataList with responsive columns. Built-in ErrorBoundary, loading/skeleton/empty/error states.
1365
+
1366
+ WHEN TO USE:
1367
+ • Card grids: products, gallery, team members, posts
1368
+ • Need responsive column count → pass { base: 1, sm: 2, lg: 3, xl: 4 }
1369
+ • Single column / row layout → DataList
1370
+ • Tabular data → table component (not DataGrid)
1371
+ • Huge dataset → VirtualGrid
1372
+
1373
+ ANTI-PATTERNS:
1374
+ ✗ Tabular data forced into card grid (use a table)
1375
+ ✗ Hardcoded column count for responsive layouts → use responsive object
1376
+ ✗ Manual loading/empty handling → DataGrid does it
1377
+
1378
+ | Prop | Type | Default | Description |
1379
+ |---|---|---|---|
1380
+ | `list` | `ReactNode`[] | - | Data array to render. null = loading state (required) |
1381
+ | `columns` | `number` \| `object` | - | Column count. number = fixed | { base, sm, md, lg, xl, 2xl } = responsive (required) |
1382
+ | `gap` | `number` \| `string` | - | Item gap. number = px | string = CSS value |
1383
+ | `noDataMessage` | `ReactNode` | - | Message for empty array (string | ReactElement) |
1384
+ | `errorFallback` | `ReactNode` | - | Fallback on error (ReactNode) |
1385
+ | `loadingElement` | `ReactNode` | - | Custom loading element (default: Spinner) |
1386
+ | `skeletonElement` | `ReactNode` | - | Skeleton element during loading (ReactElement) |
1387
+ | `skeletonCount` | `number` | `3` | Skeleton repeat count |
1388
+ | `loading` | `boolean` | `false` | Force loading state |
1389
+ | `children` | `ReactNode` | - | Item render function: ({ item, index }) => ReactNode (required) |
1390
+ | `className` | `string` | - | Root element style |
1391
+
1392
+ Card grid counterpart of `Table`. Inherits all DataList semantics (loading / skeleton / empty / error) and adds responsive `columns` + `gap`. Use this whenever you need a card grid view of data.
1393
+
1394
+ - **`columns`**: `number` for fixed, or `{ base, sm, md, lg, xl, 2xl }` for responsive (mobile-first; missing breakpoints inherit from the previous one).
1395
+ - **`gap`**: `number` (px) or any CSS gap value (e.g. `'1rem'`).
1396
+ - All other props (`list`, `skeletonElement`, `noDataMessage`, `errorFallback`, `loading`) behave exactly like `DataList`.
1397
+
1398
+ ```tsx
1399
+ // 1) Fixed columns
1400
+ <DataGrid list={projects} columns={3} gap={12}>
1401
+ {({ item }) => <ProjectCard key={item.id} project={item} />}
1402
+ </DataGrid>
1403
+
1404
+ // 2) Responsive columns (mobile-first)
1405
+ <DataGrid
1406
+ list={projects}
1407
+ columns={{ base: 1, sm: 2, lg: 3, xl: 4 }}
1408
+ gap={12}
1409
+ >
1410
+ {({ item }) => <ProjectCard key={item.id} project={item} />}
1411
+ </DataGrid>
1412
+
1413
+ // 3) Skeleton loading (auto-shown when list is null)
1414
+ <DataGrid
1415
+ list={projects}
1416
+ columns={{ base: 1, md: 2, lg: 3 }}
1417
+ gap={12}
1418
+ skeletonElement={<ProjectCardSkeleton />}
1419
+ skeletonCount={6}
1420
+ >
1421
+ {({ item }) => <ProjectCard key={item.id} project={item} />}
1422
+ </DataGrid>
1423
+
1424
+ // 4) Empty / error states
1425
+ <DataGrid
1426
+ list={projects}
1427
+ columns={3}
1428
+ gap={12}
1429
+ noDataMessage="No projects"
1430
+ errorFallback={<div>Failed to load</div>}
1431
+ >
1432
+ {({ item }) => <ProjectCard key={item.id} project={item} />}
1433
+ </DataGrid>
1434
+ ```
1435
+
1436
+ **When to use `Table` vs `DataGrid`**:
1437
+ - Tabular data with rows/columns → `Table` + `TableRow` + `TdColumn`
1438
+ - Card-style items in a responsive grid → `DataGrid`
1439
+ - Either view of the same data (toggle pattern) → both, switched by a state-driven `ToggleGroup`
1440
+
1441
+ ---
1442
+
894
1443
  ## InfiniteScroll
895
1444
 
896
- Infinite scroll. Based on IntersectionObserver.
1445
+ InfiniteScroll — auto-load more when sentinel enters viewport. Based on IntersectionObserver.
1446
+
1447
+ WHEN TO USE:
1448
+ • Feeds, social timelines, search-as-you-scroll
1449
+ • Continuous browsing where total isn't critical
1450
+ • Need jump-to-page → Pagination (not InfiniteScroll)
1451
+ • Need to render 1000s of already-loaded items efficiently → VirtualList
1452
+ • Combine with VirtualList for huge + paginated data
1453
+
1454
+ Pass either totalCount or hasMore (not both). handleLoadMore is required.
1455
+
1456
+ ANTI-PATTERNS:
1457
+ ✗ InfiniteScroll without footer/loading element (looks broken at the bottom)
1458
+ ✗ InfiniteScroll without debouncing handleLoadMore — duplicate fetches
1459
+ ✗ Auto-loading critical actions in footer (links, contact) — they become unreachable
897
1460
 
898
1461
  | Prop | Type | Default | Description |
899
1462
  |---|---|---|---|
@@ -924,7 +1487,20 @@ Infinite scroll. Based on IntersectionObserver.
924
1487
 
925
1488
  ## Ellipsis
926
1489
 
927
- Text ellipsis. Built-in show more/less toggle.
1490
+ Ellipsis clamp long text to N lines with "show more / less" toggle.
1491
+
1492
+ WHEN TO USE:
1493
+ • Long descriptions in cards, comments, post previews
1494
+ • Single-line truncation only → CSS line-clamp utility (lighter)
1495
+ • Tooltip on hover for full text → Tooltip + truncate utility
1496
+ • Critical content that must be fully readable — don't truncate
1497
+
1498
+ triggerMore/triggerLess accept ReactNode for icons or styled buttons.
1499
+
1500
+ ANTI-PATTERNS:
1501
+ ✗ Ellipsis on titles / labels — confusing UX
1502
+ ✗ Always-collapsed (defaultShortened=true) for short text — measurement waste
1503
+ ✗ Overriding triggerMore/Less with HTML that breaks accessibility (use button-like)
928
1504
 
929
1505
  | Prop | Type | Default | Description |
930
1506
  |---|---|---|---|
@@ -947,6 +1523,16 @@ Text ellipsis. Built-in show more/less toggle.
947
1523
 
948
1524
  Number input with two variants: basic (chevron arrows) and bind (+/- buttons). Supports label, description, max display (click to fill), accelerated increment on long press. numberInputBind(ref, direction) binds acceleration to external buttons.
949
1525
 
1526
+ WHEN TO USE:
1527
+ • Any numeric field — quantity, score, age, count
1528
+ • Currency with thousand separators → PriceInput instead
1529
+ • Date / time → DatePicker instead
1530
+
1531
+ ANTI-PATTERNS:
1532
+ ✗ <TextInput type="number"> → <NumberInput> (loses keyboard ↑↓, step, accelerated long-press, max click)
1533
+ ✗ Custom +/- buttons + <input> → variant="bind" (or numberInputBind for external)
1534
+ ✗ Manual thousand separators for currency → PriceInput
1535
+
950
1536
  | Prop | Type | Default | Description |
951
1537
  |---|---|---|---|
952
1538
  | `variant` | `'basic'` \| `'bind'` | `"basic"` | Variant: basic (right chevron arrows) or bind (left/right +/- buttons) |
@@ -1001,7 +1587,20 @@ const ref = useRef<NumberInputRef>(null);
1001
1587
 
1002
1588
  ## PriceInput
1003
1589
 
1004
- Price/amount input field with prefix, suffix, balance display, and auto-fill on balance click.
1590
+ PriceInput — currency / amount input with prefix, suffix, balance display, click-to-fill.
1591
+
1592
+ WHEN TO USE:
1593
+ • Money / token amounts: payment, transfer, swap, withdraw
1594
+ • Need balance hint with auto-fill UX (set balance + onBalanceClick)
1595
+ • Pure number (count, age, score) → NumberInput
1596
+ • Generic text → TextInput
1597
+
1598
+ separator=true displays commas; onValueChange always returns raw value (no commas). maxBalance auto-applies error styling on overflow.
1599
+
1600
+ ANTI-PATTERNS:
1601
+ ✗ <TextInput type="number"> + manual currency formatting → PriceInput
1602
+ ✗ NumberInput for currency (loses prefix/suffix/balance UX)
1603
+ ✗ Using string input for amounts then parseFloat — lose precision; use this component
1005
1604
 
1006
1605
  | Prop | Type | Default | Description |
1007
1606
  |---|---|---|---|
@@ -1029,7 +1628,21 @@ Price/amount input field with prefix, suffix, balance display, and auto-fill on
1029
1628
 
1030
1629
  ## Badge
1031
1630
 
1032
- Badge indicator. Dot or count display. Wraps children when provided.
1631
+ Badge small status / count indicator overlaid on an anchor (icon, avatar, button).
1632
+
1633
+ WHEN TO USE:
1634
+ • Notification count on bell icon
1635
+ • Unread message count on inbox tab
1636
+ • Status dot (online/offline) → dot=true
1637
+ • Status label with text (e.g. "PRO", "NEW") → Chip variant="accent"
1638
+ • Removable filter token → Chip with onClose
1639
+
1640
+ count={0} hides badge by default; pass showZero=true to keep visible.
1641
+
1642
+ ANTI-PATTERNS:
1643
+ ✗ Using Badge as standalone label without anchor → Chip
1644
+ ✗ count > 999 without max → ugly layout; default max=99 ("99+")
1645
+ ✗ Multiple badges on same anchor (visual noise)
1033
1646
 
1034
1647
  | Prop | Type | Default | Description |
1035
1648
  |---|---|---|---|
@@ -1047,7 +1660,21 @@ Badge indicator. Dot or count display. Wraps children when provided.
1047
1660
 
1048
1661
  ## Progress
1049
1662
 
1050
- Progress bar. Linear progress indicator with percentage display.
1663
+ Progress linear progress bar (file upload, multi-step form, loading with known %).
1664
+
1665
+ WHEN TO USE:
1666
+ • Quantifiable progress (% complete, X of Y)
1667
+ • Unknown duration spinner → Spinner
1668
+ • Stable component shape during load → Skeleton
1669
+ • Step-based navigation → Stepper
1670
+ • Indeterminate (loading without %) → set indeterminate=true
1671
+
1672
+ variant follows semantic colors (success when complete, danger on error).
1673
+
1674
+ ANTI-PATTERNS:
1675
+ ✗ Indeterminate Progress for short loads (<1s) — Spinner is lighter
1676
+ ✗ Custom <div style={{ width: x% }}> → Progress (a11y, tokens)
1677
+ ✗ Progress without label/showValue when % is critical info
1051
1678
 
1052
1679
  | Prop | Type | Default | Description |
1053
1680
  |---|---|---|---|
@@ -1064,7 +1691,20 @@ Progress bar. Linear progress indicator with percentage display.
1064
1691
 
1065
1692
  ## Alert
1066
1693
 
1067
- Alert / Banner. Inline notification with icon, title, description.
1694
+ Alert persistent inline notification (banner). Auto icon by variant.
1695
+
1696
+ WHEN TO USE:
1697
+ • In-page status: form errors, server warnings, info banners, success confirmation
1698
+ • Transient toast/snackbar → use a toast library, NOT Alert
1699
+ • Modal-blocking error → Modal with semantic="danger"
1700
+ • Inline form field error → use error/description prop on TextInput / Select / etc.
1701
+
1702
+ variant maps to semantic colors. closable=true gives users dismiss control. action prop reserved for inline buttons (e.g. "Retry").
1703
+
1704
+ ANTI-PATTERNS:
1705
+ ✗ Stacking 5 alerts at top of page (use one with summary)
1706
+ ✗ Critical destructive action confirmation in Alert → Modal
1707
+ ✗ Wrapping <div className="bg-red-100"> manually → Alert (a11y role + tokens)
1068
1708
 
1069
1709
  | Prop | Type | Default | Description |
1070
1710
  |---|---|---|---|
@@ -1081,7 +1721,20 @@ Alert / Banner. Inline notification with icon, title, description.
1081
1721
 
1082
1722
  ## EmptyState
1083
1723
 
1084
- Empty state placeholder. Shown when data is empty or unavailable.
1724
+ EmptyState friendly placeholder for empty lists, no search results, first-time setup.
1725
+
1726
+ WHEN TO USE:
1727
+ • Empty inbox / list / search results
1728
+ • First-time use (onboarding nudge with action button)
1729
+ • Loading → Skeleton/Spinner (NOT EmptyState)
1730
+ • Error → Alert (or pass icon + title to EmptyState only when conceptually "nothing here")
1731
+
1732
+ DataList/DataGrid have built-in noDataMessage that wraps EmptyState — use that prop instead of conditional rendering.
1733
+
1734
+ ANTI-PATTERNS:
1735
+ ✗ Showing EmptyState during loading (confuses users into thinking data is missing)
1736
+ ✗ EmptyState without action when user can fix it ("Create your first X" button)
1737
+ ✗ Cluttered EmptyState (too much text/multiple CTAs) — keep one primary action
1085
1738
 
1086
1739
  | Prop | Type | Default | Description |
1087
1740
  |---|---|---|---|
@@ -1097,7 +1750,21 @@ Empty state placeholder. Shown when data is empty or unavailable.
1097
1750
 
1098
1751
  ## Breadcrumb
1099
1752
 
1100
- Breadcrumb navigation (compound component pattern). Use <Breadcrumb.Item> children instead of items array. Each Item can wrap arbitrary ReactNode (Link, Select, plain text, etc.).
1753
+ Breadcrumb hierarchical location navigation (Home > Settings > Profile).
1754
+
1755
+ WHEN TO USE:
1756
+ • Multi-level information architecture (>=3 levels deep)
1757
+ • Hierarchical category drill-down (file system, taxonomy)
1758
+ • Step-based linear flow → Stepper (not Breadcrumb)
1759
+ • Tab switching → Tab
1760
+ • Single-level navigation → page title alone
1761
+
1762
+ Compound pattern: use <Breadcrumb.Item> children. Each Item wraps any ReactNode (Link, Select, plain text). Use maxItems for long paths (auto-collapse with "…").
1763
+
1764
+ ANTI-PATTERNS:
1765
+ ✗ Breadcrumb with 1 level — just show page title
1766
+ ✗ Breadcrumb with > 6 visible items → set maxItems
1767
+ ✗ Last item as a link (it's the current page; should be plain text)
1101
1768
 
1102
1769
  | Prop | Type | Default | Description |
1103
1770
  |---|---|---|---|
@@ -1110,7 +1777,21 @@ Breadcrumb navigation (compound component pattern). Use <Breadcrumb.Item> childr
1110
1777
 
1111
1778
  ## Stepper
1112
1779
 
1113
- Stepper. Step-by-step progress indicator.
1780
+ Stepper — multi-step linear flow indicator (checkout, onboarding, wizard).
1781
+
1782
+ WHEN TO USE:
1783
+ • Sequential workflow with finite steps (3-7)
1784
+ • Show user's current position in flow + completed/upcoming steps
1785
+ • Independent navigation between unrelated sections → Tab
1786
+ • Continuous % progress → Progress
1787
+ • Hierarchy / location → Breadcrumb
1788
+
1789
+ status="error" highlights the current step with danger color when validation fails.
1790
+
1791
+ ANTI-PATTERNS:
1792
+ ✗ Stepper with 2 steps (use Progress or just navigation)
1793
+ ✗ Stepper with 10+ steps (overwhelming) — chunk into sub-flows
1794
+ ✗ Letting users skip ahead by clicking future steps (only mark completed → current is allowed)
1114
1795
 
1115
1796
  | Prop | Type | Default | Description |
1116
1797
  |---|---|---|---|
@@ -1125,7 +1806,22 @@ Stepper. Step-by-step progress indicator.
1125
1806
 
1126
1807
  ## DropdownMenu
1127
1808
 
1128
- Dropdown menu. Based on Radix DropdownMenu. Action menu for context/more menus.
1809
+ Dropdown menu — list of actions/commands triggered by a button. Based on Radix DropdownMenu (role="menu" + keyboard nav).
1810
+
1811
+ WHEN TO USE:
1812
+ • "More" / context menu (•••, ⋮)
1813
+ • Action lists where each item triggers a function (save, share, delete)
1814
+ • Selecting a value (form field) → Select / Combobox (not DropdownMenu)
1815
+ • Multi-select toggles → ToggleGroup or CheckBox group
1816
+ • Anchored info panel → Popover
1817
+
1818
+ Use danger=true on destructive items (Delete) for visual + semantic emphasis.
1819
+ Use separator=true to group related actions.
1820
+
1821
+ ANTI-PATTERNS:
1822
+ ✗ DropdownMenu for value selection submitted later → Select / Combobox
1823
+ ✗ Custom <div onClick> with isOpen state → DropdownMenu
1824
+ ✗ Long form inputs inside menu items → Popover instead
1129
1825
 
1130
1826
  | Prop | Type | Default | Description |
1131
1827
  |---|---|---|---|
@@ -1140,7 +1836,27 @@ Dropdown menu. Based on Radix DropdownMenu. Action menu for context/more menus.
1140
1836
 
1141
1837
  ## ToggleGroup
1142
1838
 
1143
- Toggle group / Segment control. Based on Radix ToggleGroup.
1839
+ Toggle group / Segment control for immediate-effect option selection. Based on Radix ToggleGroup.
1840
+
1841
+ WHEN TO USE:
1842
+ • Sort order, view mode (grid/list), filter chips — selection takes effect immediately
1843
+ • Visual comparison important (icons or short labels)
1844
+ • Form field that submits later → RadioGroup or CheckBox instead
1845
+ • Page area switching (settings tabs) → Tab instead
1846
+
1847
+ VARIANTS:
1848
+ • default — slider-style background (sleek)
1849
+ • primary / secondary — accent-filled selected state
1850
+ • outline — bordered buttons with no background (button-style toolbar)
1851
+
1852
+ fullWidth=true distributes items equally across parent width (use with outline variant for button-row look).
1853
+
1854
+ type="single" requires at least one selection by default (required=true). Set required=false to allow deselection.
1855
+
1856
+ ANTI-PATTERNS:
1857
+ ✗ Using ToggleGroup for content tabs → Tab
1858
+ ✗ Using ToggleGroup as form field submitted later → RadioGroup (semantics + a11y)
1859
+ ✗ Mixing icons and text labels of different sizes — keep visual rhythm consistent
1144
1860
 
1145
1861
  | Prop | Type | Default | Description |
1146
1862
  |---|---|---|---|
@@ -1149,16 +1865,32 @@ Toggle group / Segment control. Based on Radix ToggleGroup.
1149
1865
  | `value` | `ReactNode` | - | Controlled value (string | string[]) |
1150
1866
  | `defaultValue` | `ReactNode` | - | Default value |
1151
1867
  | `onValueChange` | `ReactNode` | - | Value change callback |
1152
- | `variant` | `'default'` \| `'outline'` | `"default"` | Style variant |
1868
+ | `variant` | `'default'` \| `'primary'` \| `'secondary'` \| `'outline'` | `"default"` | Style variant (default=slider, primary/secondary=accent filled, outline=bordered buttons with no background) |
1153
1869
  | `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
1870
+ | `fullWidth` | `boolean` | `false` | Stretch the group to parent width and split items equally |
1154
1871
  | `disabled` | `boolean` | - | Disable all items |
1872
+ | `required` | `boolean` | `true` | Prevent deselection (at least one must be selected) |
1155
1873
  | `className` | `string` | - | Style override |
1156
1874
 
1157
1875
  ---
1158
1876
 
1159
1877
  ## Slider
1160
1878
 
1161
- Slider / Range input. Based on Radix Slider. Supports single and range mode.
1879
+ Slider numeric range selector. Based on Radix Slider. Single thumb or range (two thumbs).
1880
+
1881
+ WHEN TO USE:
1882
+ • Continuous range with visual feedback: volume, brightness, price filter
1883
+ • Range filter (min-max) → defaultValue=[20, 80]
1884
+ • Precise number entry → NumberInput
1885
+ • Discrete few options → ToggleGroup or RadioGroup
1886
+ • Boolean → Switch
1887
+
1888
+ step controls increments. onValueCommit fires only after pointer release (good for expensive recomputations).
1889
+
1890
+ ANTI-PATTERNS:
1891
+ ✗ Slider for required precise input (typing 47 is faster than dragging)
1892
+ ✗ Long step count without showValue / labels (users have no reference)
1893
+ ✗ Calling expensive API on every onValueChange tick → use onValueCommit
1162
1894
 
1163
1895
  | Prop | Type | Default | Description |
1164
1896
  |---|---|---|---|
@@ -1180,7 +1912,20 @@ Slider / Range input. Based on Radix Slider. Supports single and range mode.
1180
1912
 
1181
1913
  ## TagInput
1182
1914
 
1183
- Tag input. Enter key to add, Backspace to delete last tag.
1915
+ TagInput free-form multi-value entry (Enter to add, Backspace to remove last).
1916
+
1917
+ WHEN TO USE:
1918
+ • Free-form labels: skills, hashtags, email recipients, keywords
1919
+ • Predefined options → Combobox with multi-select (NOT TagInput)
1920
+ • Single value → TextInput
1921
+ • Limited options → Select / RadioGroup / Checkbox
1922
+
1923
+ allowDuplicates=false (default) prevents repeats. Use max to cap count.
1924
+
1925
+ ANTI-PATTERNS:
1926
+ ✗ TagInput for predefined taxonomy → Combobox (controlled options)
1927
+ ✗ TagInput without max for spam-prone fields
1928
+ ✗ Custom comma-split string field → TagInput (proper UX + a11y)
1184
1929
 
1185
1930
  | Prop | Type | Default | Description |
1186
1931
  |---|---|---|---|
@@ -1200,7 +1945,20 @@ Tag input. Enter key to add, Backspace to delete last tag.
1200
1945
 
1201
1946
  ## NxImage
1202
1947
 
1203
- Enhanced image. Lazy loading, fallback, aspect-ratio support.
1948
+ NxImage lazy-loaded <img> with fallback, fixed aspect-ratio, object-fit. Always prefer over native <img>.
1949
+
1950
+ WHEN TO USE:
1951
+ • Content images: covers, banners, illustrations, hero images
1952
+ • Profile/user avatar (with text fallback) → Avatar
1953
+ • Background image / decorative → CSS bg-image
1954
+ • Need Next.js optimization → use next/image directly (NxImage doesn't optimize)
1955
+
1956
+ aspectRatio reserves space, preventing CLS (layout shift).
1957
+
1958
+ ANTI-PATTERNS:
1959
+ ✗ <img> with manual onError handling → NxImage (built-in fallback)
1960
+ ✗ NxImage without alt for content images (a11y violation)
1961
+ ✗ NxImage without aspectRatio in fluid layouts → CLS jank
1204
1962
 
1205
1963
  | Prop | Type | Default | Description |
1206
1964
  |---|---|---|---|
@@ -1218,7 +1976,20 @@ Enhanced image. Lazy loading, fallback, aspect-ratio support.
1218
1976
 
1219
1977
  ## DatePicker
1220
1978
 
1221
- DatePicker. Calendar popup for date selection. Based on react-day-picker.
1979
+ DatePicker calendar popup for date selection. Based on react-day-picker.
1980
+
1981
+ WHEN TO USE:
1982
+ • Single date selection: birthday, due date, appointment
1983
+ • Date range → use two DatePickers with minDate/maxDate cross-bound
1984
+ • Time selection → not yet supported; use TextInput with type="time" + DatePicker combo
1985
+ • Quick relative ranges (Today/Last 7 days) → DropdownMenu of preset Buttons + DatePicker for "Custom"
1986
+
1987
+ minDate/maxDate disable out-of-range dates. locale="ko" for Korean labels.
1988
+
1989
+ ANTI-PATTERNS:
1990
+ ✗ Native <input type="date"> for branded UI (inconsistent across browsers) → DatePicker
1991
+ ✗ Free-text date with parsing → DatePicker (consistent UX)
1992
+ ✗ DatePicker without minDate for past-date-invalid fields (e.g. booking)
1222
1993
 
1223
1994
  | Prop | Type | Default | Description |
1224
1995
  |---|---|---|---|
@@ -1237,7 +2008,19 @@ DatePicker. Calendar popup for date selection. Based on react-day-picker.
1237
2008
 
1238
2009
  ## ImageUpload
1239
2010
 
1240
- ImageUpload. Drag-and-drop image upload with preview, file validation, and field label/description support.
2011
+ ImageUpload — drag-and-drop image upload with preview, file-type/size validation, label/description.
2012
+
2013
+ WHEN TO USE:
2014
+ • Single image upload: avatar, cover, KYC document, post thumbnail
2015
+ • Multiple files / non-image → not yet supported; build custom or use a file dropzone
2016
+ • Inline image picker without preview → custom <input type="file">
2017
+
2018
+ accept whitelist + maxSize together cover validation. onError fires with i18n-ready string.
2019
+
2020
+ ANTI-PATTERNS:
2021
+ ✗ <input type="file"> + manual preview → ImageUpload (handles drag, validation, preview)
2022
+ ✗ ImageUpload without onError handler → silent failure on validation reject
2023
+ ✗ Storing image as data-URL value (use File via onChange and upload to server)
1241
2024
 
1242
2025
  | Prop | Type | Default | Description |
1243
2026
  |---|---|---|---|