@nexus-cross/design-system 1.0.14 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-rules/nexus/CLAUDE.md +85 -0
- package/claude-rules/nexus/commands/nexus-audit.md +79 -0
- package/claude-rules/nexus/commands/nexus-component-map.md +85 -0
- package/claude-rules/nexus/commands/nexus-token-check.md +68 -0
- package/claude-rules/nexus/skills/nexus-design-system/SKILL.md +92 -0
- package/cursor-rules/nexus-ui-api.mdc +910 -41
- package/cursor-rules/nexus-ui-decisions.mdc +263 -0
- package/dist/accordion.js +0 -1
- package/dist/accordion.mjs +0 -1
- package/dist/alert.js +0 -1
- package/dist/alert.mjs +0 -1
- package/dist/avatar.js +0 -1
- package/dist/avatar.mjs +0 -1
- package/dist/badge.js +0 -1
- package/dist/badge.mjs +0 -1
- package/dist/breadcrumb.js +0 -1
- package/dist/breadcrumb.mjs +0 -1
- package/dist/button.js +0 -1
- package/dist/button.mjs +0 -1
- package/dist/carousel.js +0 -1
- package/dist/carousel.mjs +0 -1
- package/dist/checkbox.js +0 -1
- package/dist/checkbox.mjs +0 -1
- package/dist/chip.js +0 -1
- package/dist/chip.mjs +0 -1
- package/dist/chunks/chunk-2Z52NPWB.js +78 -0
- package/dist/chunks/chunk-3SCSND6S.js +7 -0
- package/dist/chunks/chunk-46P52MFM.mjs +56 -0
- package/dist/chunks/chunk-AG2UJPFX.mjs +621 -0
- package/dist/chunks/{chunk-33UFQJIO.mjs → chunk-BJMXZJWO.mjs} +16 -5
- package/dist/chunks/{chunk-YZV6FWE7.js → chunk-JLDQNDFT.js} +16 -5
- package/dist/chunks/{chunk-K574BYHQ.js → chunk-K3CK7NTP.js} +22 -4
- package/dist/chunks/{chunk-Z4YM7LU3.mjs → chunk-PIGHBDK5.mjs} +22 -4
- package/dist/chunks/chunk-QWK4CLS2.mjs +5 -0
- package/dist/chunks/chunk-RC2Y4UH7.js +648 -0
- package/dist/chunks/{chunk-PEIEVKD5.js → chunk-RCIBLLSF.js} +11 -12
- package/dist/chunks/{chunk-K2TBLM3F.mjs → chunk-THBE27U3.mjs} +11 -12
- package/dist/client-only.js +0 -1
- package/dist/client-only.mjs +0 -1
- package/dist/combobox.js +28 -0
- package/dist/combobox.mjs +3 -0
- package/dist/components/Combobox.d.ts +93 -0
- package/dist/components/Combobox.d.ts.map +1 -0
- package/dist/components/DataGrid.d.ts +44 -0
- package/dist/components/DataGrid.d.ts.map +1 -0
- package/dist/components/DataList.d.ts +3 -1
- package/dist/components/DataList.d.ts.map +1 -1
- package/dist/components/RadioGroup.d.ts +4 -0
- package/dist/components/RadioGroup.d.ts.map +1 -1
- package/dist/components/ToggleGroup.d.ts +2 -1
- package/dist/components/ToggleGroup.d.ts.map +1 -1
- package/dist/countdown.js +0 -1
- package/dist/countdown.mjs +0 -1
- package/dist/counter.js +0 -1
- package/dist/counter.mjs +0 -1
- package/dist/data-grid.js +14 -0
- package/dist/data-grid.mjs +5 -0
- package/dist/data-list.js +2 -3
- package/dist/data-list.mjs +1 -2
- package/dist/date-picker.js +0 -1
- package/dist/date-picker.mjs +0 -1
- package/dist/divider.js +0 -1
- package/dist/divider.mjs +0 -1
- package/dist/drawer.js +0 -1
- package/dist/drawer.mjs +0 -1
- package/dist/dropdown-menu.js +0 -1
- package/dist/dropdown-menu.mjs +0 -1
- package/dist/ellipsis.js +0 -1
- package/dist/ellipsis.mjs +0 -1
- package/dist/empty-state.js +0 -1
- package/dist/empty-state.mjs +0 -1
- package/dist/error-boundary.js +0 -1
- package/dist/error-boundary.mjs +0 -1
- package/dist/hooks/useCheckDevice.js +0 -1
- package/dist/hooks/useCheckDevice.mjs +0 -1
- package/dist/hooks/useClickOutside.js +0 -1
- package/dist/hooks/useClickOutside.mjs +0 -1
- package/dist/hooks/useDraggableBottomSheet.js +0 -1
- package/dist/hooks/useDraggableBottomSheet.mjs +0 -1
- package/dist/hooks/useDraggableWindow.js +0 -1
- package/dist/hooks/useDraggableWindow.mjs +0 -1
- package/dist/hooks/useInView.js +0 -1
- package/dist/hooks/useInView.mjs +0 -1
- package/dist/hooks/useModal.js +0 -1
- package/dist/hooks/useModal.mjs +0 -1
- package/dist/image-upload.js +0 -1
- package/dist/image-upload.mjs +0 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -85
- package/dist/index.mjs +11 -11
- package/dist/infinite-scroll.js +0 -1
- package/dist/infinite-scroll.mjs +0 -1
- package/dist/marquee.js +0 -1
- package/dist/marquee.mjs +0 -1
- package/dist/modal/index.js +11 -12
- package/dist/modal/index.mjs +2 -3
- package/dist/number-input.js +0 -1
- package/dist/number-input.mjs +0 -1
- package/dist/nx-image.js +0 -1
- package/dist/nx-image.mjs +0 -1
- package/dist/pagination.js +0 -1
- package/dist/pagination.mjs +0 -1
- package/dist/popover.js +0 -1
- package/dist/popover.mjs +0 -1
- package/dist/price-input.js +0 -1
- package/dist/price-input.mjs +0 -1
- package/dist/progress.js +0 -1
- package/dist/progress.mjs +0 -1
- package/dist/radio-group.js +5 -6
- package/dist/radio-group.mjs +1 -2
- package/dist/schemas/_all.json +353 -117
- package/dist/schemas/accordion.d.ts.map +1 -1
- package/dist/schemas/accordion.json +1 -1
- package/dist/schemas/alert.d.ts.map +1 -1
- package/dist/schemas/alert.json +1 -1
- package/dist/schemas/avatar.d.ts.map +1 -1
- package/dist/schemas/avatar.json +1 -1
- package/dist/schemas/badge.d.ts.map +1 -1
- package/dist/schemas/badge.json +1 -1
- package/dist/schemas/breadcrumb.d.ts.map +1 -1
- package/dist/schemas/breadcrumb.json +1 -1
- package/dist/schemas/button.d.ts.map +1 -1
- package/dist/schemas/button.json +1 -1
- package/dist/schemas/carousel.d.ts.map +1 -1
- package/dist/schemas/carousel.json +1 -1
- package/dist/schemas/checkBox.json +1 -1
- package/dist/schemas/checkbox.d.ts.map +1 -1
- package/dist/schemas/chip.d.ts.map +1 -1
- package/dist/schemas/chip.json +1 -1
- package/dist/schemas/client-only.d.ts.map +1 -1
- package/dist/schemas/clientOnly.json +1 -1
- package/dist/schemas/combobox.d.ts +108 -0
- package/dist/schemas/combobox.d.ts.map +1 -0
- package/dist/schemas/combobox.json +98 -0
- package/dist/schemas/comboboxOption.json +35 -0
- package/dist/schemas/comboboxOptionDescription.json +20 -0
- package/dist/schemas/comboboxOptionMeta.json +20 -0
- package/dist/schemas/countdown.d.ts.map +1 -1
- package/dist/schemas/countdown.json +1 -1
- package/dist/schemas/counter.d.ts.map +1 -1
- package/dist/schemas/counter.json +1 -1
- package/dist/schemas/data-grid.d.ts +74 -0
- package/dist/schemas/data-grid.d.ts.map +1 -0
- package/dist/schemas/data-list.d.ts.map +1 -1
- package/dist/schemas/dataGrid.json +102 -0
- package/dist/schemas/dataList.json +1 -1
- package/dist/schemas/date-picker.d.ts.map +1 -1
- package/dist/schemas/datePicker.json +1 -1
- package/dist/schemas/divider.d.ts.map +1 -1
- package/dist/schemas/divider.json +1 -1
- package/dist/schemas/drawer.d.ts.map +1 -1
- package/dist/schemas/drawer.json +1 -1
- package/dist/schemas/dropdown-menu.d.ts.map +1 -1
- package/dist/schemas/dropdownMenu.json +1 -1
- package/dist/schemas/ellipsis.d.ts.map +1 -1
- package/dist/schemas/ellipsis.json +1 -1
- package/dist/schemas/empty-state.d.ts.map +1 -1
- package/dist/schemas/emptyState.json +1 -1
- package/dist/schemas/error-boundary.d.ts.map +1 -1
- package/dist/schemas/errorBoundary.json +1 -1
- package/dist/schemas/image-upload.d.ts.map +1 -1
- package/dist/schemas/imageUpload.json +1 -1
- package/dist/schemas/index.d.ts +2 -1
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/infinite-scroll.d.ts.map +1 -1
- package/dist/schemas/infiniteScroll.json +1 -1
- package/dist/schemas/marquee.d.ts.map +1 -1
- package/dist/schemas/marquee.json +1 -1
- package/dist/schemas/modal.d.ts.map +1 -1
- package/dist/schemas/modalTemplate.json +1 -1
- package/dist/schemas/number-input.d.ts.map +1 -1
- package/dist/schemas/numberInput.json +1 -1
- package/dist/schemas/nx-image.d.ts.map +1 -1
- package/dist/schemas/nxImage.json +1 -1
- package/dist/schemas/pagination.d.ts.map +1 -1
- package/dist/schemas/pagination.json +1 -1
- package/dist/schemas/popover.d.ts.map +1 -1
- package/dist/schemas/popover.json +1 -1
- package/dist/schemas/price-input.d.ts.map +1 -1
- package/dist/schemas/priceInput.json +1 -1
- package/dist/schemas/progress.d.ts.map +1 -1
- package/dist/schemas/progress.json +1 -1
- package/dist/schemas/radio-group.d.ts +9 -0
- package/dist/schemas/radio-group.d.ts.map +1 -1
- package/dist/schemas/radioGroup.json +10 -1
- package/dist/schemas/radioItem.json +11 -0
- package/dist/schemas/select.d.ts.map +1 -1
- package/dist/schemas/select.json +1 -1
- package/dist/schemas/skeleton.d.ts.map +1 -1
- package/dist/schemas/skeleton.json +1 -1
- package/dist/schemas/slider.d.ts.map +1 -1
- package/dist/schemas/slider.json +1 -1
- package/dist/schemas/spinner.d.ts.map +1 -1
- package/dist/schemas/spinner.json +1 -1
- package/dist/schemas/stepper.d.ts.map +1 -1
- package/dist/schemas/stepper.json +1 -1
- package/dist/schemas/switch.d.ts.map +1 -1
- package/dist/schemas/switch.json +1 -1
- package/dist/schemas/tab.d.ts.map +1 -1
- package/dist/schemas/tab.json +1 -1
- package/dist/schemas/table.d.ts.map +1 -1
- package/dist/schemas/table.json +1 -1
- package/dist/schemas/tableRow.json +1 -1
- package/dist/schemas/tag-input.d.ts.map +1 -1
- package/dist/schemas/tagInput.json +1 -1
- package/dist/schemas/tdColumn.json +1 -1
- package/dist/schemas/text-area.d.ts.map +1 -1
- package/dist/schemas/text-input.d.ts +2 -2
- package/dist/schemas/text-input.d.ts.map +1 -1
- package/dist/schemas/textArea.json +1 -1
- package/dist/schemas/textInput.json +1 -1
- package/dist/schemas/toast.d.ts.map +1 -1
- package/dist/schemas/toastOptions.json +1 -1
- package/dist/schemas/toaster.json +1 -1
- package/dist/schemas/toggle-group.d.ts +6 -3
- package/dist/schemas/toggle-group.d.ts.map +1 -1
- package/dist/schemas/toggleGroup.json +9 -3
- package/dist/schemas/tooltip.d.ts.map +1 -1
- package/dist/schemas/tooltip.json +1 -1
- package/dist/schemas/virtual-scroll.d.ts.map +1 -1
- package/dist/schemas/virtualGrid.json +1 -1
- package/dist/schemas/virtualList.json +1 -1
- package/dist/schemas.js +928 -66
- package/dist/schemas.mjs +924 -66
- package/dist/select.js +0 -1
- package/dist/select.mjs +0 -1
- package/dist/skeleton.js +0 -1
- package/dist/skeleton.mjs +0 -1
- package/dist/slider.js +0 -1
- package/dist/slider.mjs +0 -1
- package/dist/spinner.js +0 -1
- package/dist/spinner.mjs +0 -1
- package/dist/stepper.js +0 -1
- package/dist/stepper.mjs +0 -1
- package/dist/styles/.generated/built.d.ts +1 -1
- package/dist/styles/.generated/built.d.ts.map +1 -1
- package/dist/styles/layer.js +2 -3
- package/dist/styles/layer.mjs +1 -2
- package/dist/styles.css +484 -13
- package/dist/styles.js +2 -3
- package/dist/styles.layered.css +484 -13
- package/dist/styles.mjs +1 -2
- package/dist/switch.js +0 -1
- package/dist/switch.mjs +0 -1
- package/dist/tab.js +0 -1
- package/dist/tab.mjs +0 -1
- package/dist/table.js +0 -1
- package/dist/table.mjs +0 -1
- package/dist/tag-input.js +0 -1
- package/dist/tag-input.mjs +0 -1
- package/dist/text-area.js +0 -1
- package/dist/text-area.mjs +0 -1
- package/dist/text-input.js +0 -1
- package/dist/text-input.mjs +0 -1
- package/dist/toast.js +0 -1
- package/dist/toast.mjs +0 -1
- package/dist/toggle-group.js +3 -4
- package/dist/toggle-group.mjs +1 -2
- package/dist/tooltip.js +0 -1
- package/dist/tooltip.mjs +0 -1
- package/dist/utils/cn.js +0 -1
- package/dist/utils/cn.mjs +0 -1
- package/dist/utils/scroll.js +0 -1
- package/dist/utils/scroll.mjs +0 -1
- package/dist/virtual-scroll.js +0 -1
- package/dist/virtual-scroll.mjs +0 -1
- package/package.json +14 -8
- package/scripts/setup-cursor-rules.cjs +164 -27
- package/dist/chunks/chunk-22ULI3BF.js +0 -21
- package/dist/chunks/chunk-CVYXRSXT.mjs +0 -8
- package/dist/chunks/chunk-I252NERB.mjs +0 -21
- package/dist/chunks/chunk-JNMCYWGY.js +0 -10
- package/dist/chunks/chunk-LAOQRXCE.js +0 -7
- package/dist/chunks/chunk-S6ODYMFP.mjs +0 -5
- package/dist/components/ThemeProvider.d.ts +0 -25
- package/dist/components/ThemeProvider.d.ts.map +0 -1
- package/dist/schemas/theme-provider.d.ts +0 -36
- package/dist/schemas/theme-provider.d.ts.map +0 -1
- package/dist/schemas/themeProvider.json +0 -65
- package/dist/theme-provider.js +0 -15
- package/dist/theme-provider.mjs +0 -2
- package/dist/chunks/{chunk-CWMLTXOH.mjs → chunk-5ZVPTIL3.mjs} +1 -1
- 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
|
|
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
|
-
|
|
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
|
|
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,239 @@ Individual option within Select.
|
|
|
194
239
|
|
|
195
240
|
---
|
|
196
241
|
|
|
242
|
+
## Combobox
|
|
243
|
+
|
|
244
|
+
Searchable select with compound option API. 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
|
+
COMPOUND API:
|
|
253
|
+
<Combobox value={v} onValueChange={setV} placeholder="…">
|
|
254
|
+
<Combobox.Option value="kr">한국</Combobox.Option>
|
|
255
|
+
<Combobox.Option value="jp" disabled>
|
|
256
|
+
일본
|
|
257
|
+
<Combobox.OptionDescription>품절</Combobox.OptionDescription>
|
|
258
|
+
<Combobox.OptionMeta>JP</Combobox.OptionMeta>
|
|
259
|
+
</Combobox.Option>
|
|
260
|
+
</Combobox>
|
|
261
|
+
|
|
262
|
+
• <Combobox.Option> requires unique `value` (dev mode warns on duplicates and drops the duplicate)
|
|
263
|
+
• <Combobox.OptionDescription> = secondary line under label
|
|
264
|
+
• <Combobox.OptionMeta> = right-aligned slot (price, shortcut, badge)
|
|
265
|
+
• Both slots are excluded from textValue-based search
|
|
266
|
+
|
|
267
|
+
ASYNC PATTERN:
|
|
268
|
+
<Combobox loading={isFetching} onSearch={(q) => mutate(q)}>
|
|
269
|
+
{results.map((u) => (
|
|
270
|
+
<Combobox.Option key={u.id} value={u.id}>{u.name}
|
|
271
|
+
<Combobox.OptionDescription>{u.email}</Combobox.OptionDescription>
|
|
272
|
+
</Combobox.Option>
|
|
273
|
+
))}
|
|
274
|
+
</Combobox>
|
|
275
|
+
— onSearch fires after searchDebounce (default 250ms). Do NOT clear input on result update; component preserves user's typing.
|
|
276
|
+
|
|
277
|
+
IME (Korean/Japanese/Chinese): Enter during composition is ignored automatically — do not add custom keydown handlers.
|
|
278
|
+
|
|
279
|
+
ANTI-PATTERNS:
|
|
280
|
+
✗ <Select> with 20 options → <Combobox>
|
|
281
|
+
✗ Manual <input> + dropdown div + filter logic → <Combobox>
|
|
282
|
+
✗ Passing options through a prop array (legacy API removed) → use <Combobox.Option> children
|
|
283
|
+
✗ Wrapping options in extra elements (<div><Combobox.Option/></div>) → keep them as direct children
|
|
284
|
+
✗ Same `value` on two <Combobox.Option> — duplicates are warned + dropped in dev mode
|
|
285
|
+
✗ Setting value externally to clear input mid-typing → use onValueChange instead
|
|
286
|
+
|
|
287
|
+
| Prop | Type | Default | Description |
|
|
288
|
+
|---|---|---|---|
|
|
289
|
+
| `children` | `ReactNode` | - | <Combobox.Option> elements (required). Other children are ignored with a dev-mode warning. Async-search consumers swap this list as `onSearch` results arrive. |
|
|
290
|
+
| `value` | `ReactNode` | - | Selected value. string for single, string[] for multiple |
|
|
291
|
+
| `defaultValue` | `ReactNode` | - | Initial value (uncontrolled) |
|
|
292
|
+
| `onValueChange` | `ReactNode` | - | Value change callback. (value: string | string[]) => void |
|
|
293
|
+
| `multiple` | `boolean` | `false` | Multi-select mode. Selected values shown as chips inside input |
|
|
294
|
+
| `onSearch` | `ReactNode` | - | Async search callback. (query: string) => void. Triggers external data fetching with debounce. When set, the built-in client filter is disabled — render whatever <Combobox.Option> children match the latest results. |
|
|
295
|
+
| `searchDebounce` | `number` | `250` | Debounce delay (ms) before onSearch fires |
|
|
296
|
+
| `loading` | `boolean` | `false` | Externally-controlled loading state. Shows spinner in input suffix and a status row inside the popover. |
|
|
297
|
+
| `filter` | `ReactNode` | - | Custom client-side filter. (option: { value, textValue, disabled }, query: string) => boolean. Default: case-insensitive textValue includes match. Ignored when onSearch is set. |
|
|
298
|
+
| `placeholder` | `string` | - | Input placeholder |
|
|
299
|
+
| `emptyMessage` | `ReactNode` | - | Message when no options match (string | ReactNode). Default: "검색 결과 없음" |
|
|
300
|
+
| `loadingMessage` | `ReactNode` | - | Message during loading state inside popover (string | ReactNode). Default: "검색 중…" |
|
|
301
|
+
| `size` | `'md'` \| `'lg'` \| `'xl'` | `"md"` | Input size (matches TextInput tokens) |
|
|
302
|
+
| `disabled` | `boolean` | - | Disabled |
|
|
303
|
+
| `error` | `boolean` | - | Error state |
|
|
304
|
+
| `clearable` | `boolean` | `true` | Show clear button when value(s) exist |
|
|
305
|
+
| `autoOpenOnFocus` | `boolean` | `true` | Open popover automatically when input gains focus |
|
|
306
|
+
| `label` | `ReactNode` | - | Field label (ReactNode) |
|
|
307
|
+
| `description` | `ReactNode` | - | Helper text below input (ReactNode) |
|
|
308
|
+
| `className` | `string` | - | Wrapper className |
|
|
309
|
+
| `popoverClassName` | `string` | - | Popover content className |
|
|
310
|
+
|
|
311
|
+
### Combobox.Option
|
|
312
|
+
|
|
313
|
+
Single Combobox option. Direct child of <Combobox> only.
|
|
314
|
+
|
|
315
|
+
WHEN TO USE:
|
|
316
|
+
• One per selectable item; `value` MUST be unique within the Combobox
|
|
317
|
+
• Wrap rich content (icons, badges) directly as children — no escape hatch needed
|
|
318
|
+
• Use textValue when label is non-text (e.g. <Combobox.Option value="apple" textValue="사과 apple">🍎</Combobox.Option>)
|
|
319
|
+
|
|
320
|
+
ANTI-PATTERNS:
|
|
321
|
+
✗ <Combobox><div><Combobox.Option/></div></Combobox> — Option must be a direct child
|
|
322
|
+
✗ Same value on two options → dev warning + silent drop; pick unique values
|
|
323
|
+
✗ Putting label text inside <Combobox.OptionDescription> — that slot is the secondary line below the label
|
|
324
|
+
|
|
325
|
+
| Prop | Type | Default | Description |
|
|
326
|
+
|---|---|---|---|
|
|
327
|
+
| `value` | `string` | - | Unique value (string, required). Duplicate values within one Combobox produce a dev-mode console.error and the duplicate option is dropped. |
|
|
328
|
+
| `disabled` | `boolean` | - | Disable selection. Skipped by keyboard navigation (Arrow Up/Down, Home/End). |
|
|
329
|
+
| `textValue` | `string` | - | Text used for client-side filtering and the input display when this option is selected. If omitted, derived from `children` (string nodes only; OptionDescription / OptionMeta are excluded). Set this when label contains icons or non-text nodes you still want searchable (e.g. textValue="apple 사과 fruit"). |
|
|
330
|
+
| `className` | `string` | - | Class merged onto the rendered <div role="option">. |
|
|
331
|
+
| `children` | `ReactNode` | - | Label content + optional <Combobox.OptionDescription> / <Combobox.OptionMeta> slots. |
|
|
332
|
+
|
|
333
|
+
### Combobox.OptionDescription
|
|
334
|
+
|
|
335
|
+
Secondary text shown below an option label. Use for hints like "Republic of Korea" beneath "한국". Excluded from textValue-based search.
|
|
336
|
+
|
|
337
|
+
| Prop | Type | Default | Description |
|
|
338
|
+
|---|---|---|---|
|
|
339
|
+
| `children` | `ReactNode` | - | Secondary text below the label (ReactNode). |
|
|
340
|
+
| `className` | `string` | - | Class for the description node. |
|
|
341
|
+
|
|
342
|
+
### Combobox.OptionMeta
|
|
343
|
+
|
|
344
|
+
Right-aligned slot inside an option. Use for prices, keyboard shortcuts, version tags, status badges. Excluded from textValue-based search.
|
|
345
|
+
|
|
346
|
+
| Prop | Type | Default | Description |
|
|
347
|
+
|---|---|---|---|
|
|
348
|
+
| `children` | `ReactNode` | - | Right-aligned meta content (price, badge, shortcut, etc.). |
|
|
349
|
+
| `className` | `string` | - | Class for the meta slot. |
|
|
350
|
+
|
|
351
|
+
Searchable select with popover listbox. **Compound API** — options are declared as <Combobox.Option> children. Supports single/multi-select and sync (auto-filter) / async (onSearch + loading) modes. **WAI-ARIA Combobox pattern** (Radix Popover under the hood — NOT Radix Select).
|
|
352
|
+
|
|
353
|
+
- **Subcomponents**: `Combobox.Option` (required `value`), `Combobox.OptionDescription` (secondary line), `Combobox.OptionMeta` (right-aligned slot).
|
|
354
|
+
- **Duplicate `value`**: dev mode logs `console.error` and silently drops the duplicate.
|
|
355
|
+
- **textValue**: derived from option children (text nodes only; OptionDescription/OptionMeta excluded). Override per-option when label is non-text.
|
|
356
|
+
- **Sync mode** (no `onSearch`): client-side filters by `textValue` (case-insensitive includes; override with `filter`).
|
|
357
|
+
- **Async mode** (provide `onSearch`): debounced (`searchDebounce`, default 250ms) callback fires for external data fetch. Set `loading` for spinner.
|
|
358
|
+
- **Multi-select** (`multiple`): chips inside input; Backspace on empty input removes the last chip.
|
|
359
|
+
- Keyboard: Arrow Up/Down, Home/End, Enter to select, Escape to close.
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
// 1) Sync — declarative options
|
|
363
|
+
<Combobox value={value} onValueChange={setValue} placeholder="국가 선택">
|
|
364
|
+
<Combobox.Option value="kr">한국</Combobox.Option>
|
|
365
|
+
<Combobox.Option value="jp">일본</Combobox.Option>
|
|
366
|
+
<Combobox.Option value="us" disabled>
|
|
367
|
+
미국
|
|
368
|
+
<Combobox.OptionDescription>일시 품절</Combobox.OptionDescription>
|
|
369
|
+
</Combobox.Option>
|
|
370
|
+
</Combobox>
|
|
371
|
+
|
|
372
|
+
// 2) Async — external search w/ loading spinner
|
|
373
|
+
const [results, setResults] = useState<{ id: string; name: string; email: string }[]>([]);
|
|
374
|
+
const [loading, setLoading] = useState(false);
|
|
375
|
+
|
|
376
|
+
const handleSearch = async (q: string) => {
|
|
377
|
+
setLoading(true);
|
|
378
|
+
setResults(await fetchUsers(q));
|
|
379
|
+
setLoading(false);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
<Combobox
|
|
383
|
+
loading={loading}
|
|
384
|
+
onSearch={handleSearch}
|
|
385
|
+
value={selectedId}
|
|
386
|
+
onValueChange={setSelectedId}
|
|
387
|
+
placeholder="유저 검색…"
|
|
388
|
+
emptyMessage="검색 결과 없음"
|
|
389
|
+
>
|
|
390
|
+
{results.map((u) => (
|
|
391
|
+
<Combobox.Option key={u.id} value={u.id}>
|
|
392
|
+
{u.name}
|
|
393
|
+
<Combobox.OptionDescription>{u.email}</Combobox.OptionDescription>
|
|
394
|
+
</Combobox.Option>
|
|
395
|
+
))}
|
|
396
|
+
</Combobox>
|
|
397
|
+
|
|
398
|
+
// 3) Multi-select with chips
|
|
399
|
+
<Combobox
|
|
400
|
+
multiple
|
|
401
|
+
value={selectedIds}
|
|
402
|
+
onValueChange={setSelectedIds}
|
|
403
|
+
loading={loading}
|
|
404
|
+
onSearch={handleSearch}
|
|
405
|
+
>
|
|
406
|
+
{results.map((u) => (
|
|
407
|
+
<Combobox.Option key={u.id} value={u.id}>{u.name}</Combobox.Option>
|
|
408
|
+
))}
|
|
409
|
+
</Combobox>
|
|
410
|
+
|
|
411
|
+
// 4) Rich option — meta slot for prices / shortcuts / badges
|
|
412
|
+
<Combobox placeholder="플랜 선택">
|
|
413
|
+
<Combobox.Option value="free">
|
|
414
|
+
Free
|
|
415
|
+
<Combobox.OptionDescription>개인용</Combobox.OptionDescription>
|
|
416
|
+
<Combobox.OptionMeta>$0/mo</Combobox.OptionMeta>
|
|
417
|
+
</Combobox.Option>
|
|
418
|
+
<Combobox.Option value="pro">
|
|
419
|
+
Pro
|
|
420
|
+
<Combobox.OptionDescription>소규모 팀</Combobox.OptionDescription>
|
|
421
|
+
<Combobox.OptionMeta>$12/mo</Combobox.OptionMeta>
|
|
422
|
+
</Combobox.Option>
|
|
423
|
+
</Combobox>
|
|
424
|
+
|
|
425
|
+
// 5) Non-text label — explicit textValue for searching
|
|
426
|
+
<Combobox.Option value="apple" textValue="apple 사과 fruit">
|
|
427
|
+
🍎 사과
|
|
428
|
+
</Combobox.Option>
|
|
429
|
+
|
|
430
|
+
// 6) With label / description / error
|
|
431
|
+
<Combobox
|
|
432
|
+
label="담당자"
|
|
433
|
+
description="검색 후 선택하세요"
|
|
434
|
+
loading={loading}
|
|
435
|
+
onSearch={handleSearch}
|
|
436
|
+
value={value}
|
|
437
|
+
onValueChange={setValue}
|
|
438
|
+
error={!value}
|
|
439
|
+
size="lg"
|
|
440
|
+
>
|
|
441
|
+
{results.map((u) => (
|
|
442
|
+
<Combobox.Option key={u.id} value={u.id}>{u.name}</Combobox.Option>
|
|
443
|
+
))}
|
|
444
|
+
</Combobox>
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**When to use `Select` vs `Combobox`**:
|
|
448
|
+
- Fixed short list, no search needed → `Select`
|
|
449
|
+
- Long list / async search / typed input → `Combobox`
|
|
450
|
+
- Free-form tag input (any string) → `TagInput`
|
|
451
|
+
|
|
452
|
+
**ANTI-PATTERNS**:
|
|
453
|
+
- ✗ `<Combobox options={[...]}>` — legacy prop API removed; use `<Combobox.Option>` children
|
|
454
|
+
- ✗ Wrapping options: `<Combobox><div><Combobox.Option/></div></Combobox>` — Option must be a direct child
|
|
455
|
+
- ✗ Duplicate `value` across options — dev warning + duplicate dropped
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
197
459
|
## CheckBox
|
|
198
460
|
|
|
199
|
-
Checkbox. Native input-based, supports square/round shapes.
|
|
461
|
+
Checkbox for multi-select form fields. Native input-based, supports square/round shapes.
|
|
462
|
+
|
|
463
|
+
WHEN TO USE:
|
|
464
|
+
• Form field with multiple independent options (T&C agreements, multi-select filters submitted later)
|
|
465
|
+
• Tri-state (parent-child selection) — use indeterminate prop
|
|
466
|
+
• Single binary that takes effect immediately → Switch instead
|
|
467
|
+
• Many options with search → Combobox (multiple)
|
|
468
|
+
|
|
469
|
+
INDETERMINATE: set indeterminate=true when some children are checked, others not. aria-checked becomes "mixed" automatically.
|
|
470
|
+
|
|
471
|
+
ANTI-PATTERNS:
|
|
472
|
+
✗ <CheckBox> for "Enable dark mode" toggle → <Switch>
|
|
473
|
+
✗ Native <input type="checkbox"> + manual styling → <CheckBox>
|
|
474
|
+
✗ Forgetting indeterminate for "select all" parent
|
|
200
475
|
|
|
201
476
|
| Prop | Type | Default | Description |
|
|
202
477
|
|---|---|---|---|
|
|
@@ -229,7 +504,24 @@ Checkbox. Native input-based, supports square/round shapes.
|
|
|
229
504
|
|
|
230
505
|
## RadioGroup
|
|
231
506
|
|
|
232
|
-
Radio group. Used with RadioItem.
|
|
507
|
+
Radio group for single-choice form fields. Used with RadioItem.
|
|
508
|
+
|
|
509
|
+
WHEN TO USE:
|
|
510
|
+
• Form field where user picks ONE of small set (≤4-7) and submits later → RadioGroup
|
|
511
|
+
• All options should be visible at once for comparison → RadioGroup (not Select)
|
|
512
|
+
• Immediate effect on selection (no submit) → ToggleGroup instead
|
|
513
|
+
• Page area switching (tabs) → Tab (not RadioGroup)
|
|
514
|
+
• Many options → Select / Combobox
|
|
515
|
+
|
|
516
|
+
VARIANTS:
|
|
517
|
+
• variant="default" — outline circle with inner dot when checked (classic)
|
|
518
|
+
• variant="ring" — thick border replaces inner dot when checked (modern, Figma "ring" style)
|
|
519
|
+
|
|
520
|
+
ANTI-PATTERNS:
|
|
521
|
+
✗ Using RadioGroup for tab-like content switching → Tab
|
|
522
|
+
✗ Using RadioGroup for immediate filters → ToggleGroup
|
|
523
|
+
✗ Mixing RadioGroup and ToggleGroup in same form → pick one pattern
|
|
524
|
+
✗ Native <input type="radio"> → RadioItem (gets a11y, focus ring, tokens)
|
|
233
525
|
|
|
234
526
|
| Prop | Type | Default | Description |
|
|
235
527
|
|---|---|---|---|
|
|
@@ -237,6 +529,7 @@ Radio group. Used with RadioItem.
|
|
|
237
529
|
| `value` | `string` | - | Selected value (controlled) |
|
|
238
530
|
| `defaultValue` | `string` | - | Initial value (uncontrolled) |
|
|
239
531
|
| `size` | `'sm'` \| `'md'` | `"md"` | Size |
|
|
532
|
+
| `variant` | `'default'` \| `'ring'` | `"default"` | Visual style. default=outline circle with inner dot, ring=thick border replaces dot when checked |
|
|
240
533
|
| `orientation` | `'horizontal'` \| `'vertical'` | `"vertical"` | Layout direction |
|
|
241
534
|
| `disabled` | `boolean` | - | Disabled |
|
|
242
535
|
| `children` | `ReactNode` | - | RadioItem list (ReactNode, required) |
|
|
@@ -253,7 +546,9 @@ Individual option within RadioGroup.
|
|
|
253
546
|
|---|---|---|---|
|
|
254
547
|
| `value` | `string` | - | Item value (required) |
|
|
255
548
|
| `size` | `'sm'` \| `'md'` | - | Size (overrides group) |
|
|
549
|
+
| `variant` | `'default'` \| `'ring'` | - | Visual style (overrides group) |
|
|
256
550
|
| `label` | `ReactNode` | - | Label text (ReactNode) |
|
|
551
|
+
| `description` | `ReactNode` | - | Help text shown beneath the label (ReactNode) |
|
|
257
552
|
| `children` | `ReactNode` | - | Label alternative content (ReactNode) |
|
|
258
553
|
| `disabled` | `boolean` | - | Disabled |
|
|
259
554
|
| `className` | `string` | - | Style override |
|
|
@@ -276,7 +571,18 @@ Individual option within RadioGroup.
|
|
|
276
571
|
|
|
277
572
|
## Switch
|
|
278
573
|
|
|
279
|
-
Toggle switch. Native checkbox-based, role="switch".
|
|
574
|
+
Toggle switch for immediate on/off binary state. Native checkbox-based, role="switch".
|
|
575
|
+
|
|
576
|
+
WHEN TO USE:
|
|
577
|
+
• Setting that takes effect immediately (notifications on/off, dark mode)
|
|
578
|
+
• Binary state with no submit step
|
|
579
|
+
• Form field submitted later → CheckBox instead (semantics: checkbox is form data)
|
|
580
|
+
• Multiple related options → CheckBox group or ToggleGroup (multiple)
|
|
581
|
+
|
|
582
|
+
ANTI-PATTERNS:
|
|
583
|
+
✗ <CheckBox> for "Enable notifications" toggle → <Switch>
|
|
584
|
+
✗ <Switch> inside form requiring submit → <CheckBox>
|
|
585
|
+
✗ Wrapping Switch in <label onClick> manually → use label prop or htmlFor pattern
|
|
280
586
|
|
|
281
587
|
| Prop | Type | Default | Description |
|
|
282
588
|
|---|---|---|---|
|
|
@@ -298,7 +604,19 @@ Toggle switch. Native checkbox-based, role="switch".
|
|
|
298
604
|
|
|
299
605
|
## Chip
|
|
300
606
|
|
|
301
|
-
Chip
|
|
607
|
+
Chip — small interactive token for filters, tags, removable selections, status labels.
|
|
608
|
+
|
|
609
|
+
WHEN TO USE:
|
|
610
|
+
• Filter token / removable selection (set onClose for X button)
|
|
611
|
+
• Tag/category indicator
|
|
612
|
+
• Status indicator with color (variant="accent")
|
|
613
|
+
• Pure count badge → Badge instead
|
|
614
|
+
• Toggleable filter → ToggleGroup or Chip with onClick + active state
|
|
615
|
+
|
|
616
|
+
ANTI-PATTERNS:
|
|
617
|
+
✗ Building dismiss UI manually with custom div + X icon → use onClose prop
|
|
618
|
+
✗ Using Chip as a primary CTA → Button
|
|
619
|
+
✗ Using Chip for hover-only labels → Tooltip
|
|
302
620
|
|
|
303
621
|
| Prop | Type | Default | Description |
|
|
304
622
|
|---|---|---|---|
|
|
@@ -326,7 +644,18 @@ Chip/tag/badge. Close button displayed via onClose prop.
|
|
|
326
644
|
|
|
327
645
|
## Spinner
|
|
328
646
|
|
|
329
|
-
Loading indicator. SVG-based.
|
|
647
|
+
Loading indicator (spinner). SVG-based. role="status" + aria-label built-in.
|
|
648
|
+
|
|
649
|
+
WHEN TO USE:
|
|
650
|
+
• Short loads (<1s), inline indicator (button content while submitting)
|
|
651
|
+
• Long loads with known structure → Skeleton (better perceived performance)
|
|
652
|
+
• Quantifiable progress → Progress
|
|
653
|
+
• Page-level loading boundary inside DataList/DataGrid → use their loading prop instead
|
|
654
|
+
|
|
655
|
+
ANTI-PATTERNS:
|
|
656
|
+
✗ Custom CSS spinning div → Spinner (a11y + tokens)
|
|
657
|
+
✗ Using Spinner where Skeleton fits (long loads with stable layout)
|
|
658
|
+
✗ Forgetting aria-label override when meaning differs from "Loading"
|
|
330
659
|
|
|
331
660
|
| Prop | Type | Default | Description |
|
|
332
661
|
|---|---|---|---|
|
|
@@ -348,6 +677,20 @@ Loading indicator. SVG-based. Built-in role="status".
|
|
|
348
677
|
|
|
349
678
|
Skeleton loading placeholder. Size/shape via className. With children, wraps transparently to maintain actual size.
|
|
350
679
|
|
|
680
|
+
WHEN TO USE:
|
|
681
|
+
• Long load with known component shape (cards, lists, profile headers)
|
|
682
|
+
• Reduces perceived wait time when layout is predictable
|
|
683
|
+
• Short loads (<1s) → Spinner
|
|
684
|
+
• Quantifiable progress → Progress
|
|
685
|
+
• DataList/DataGrid loading → set list={null} (uses skeletonElement prop automatically)
|
|
686
|
+
|
|
687
|
+
GOLDEN RULE: Skeleton must match the real component's shape and size. Mismatch causes layout shift.
|
|
688
|
+
|
|
689
|
+
ANTI-PATTERNS:
|
|
690
|
+
✗ Generic gray rectangle for everything → match real component shape
|
|
691
|
+
✗ Skeleton for instant loads (causes flash)
|
|
692
|
+
✗ Many skeletons of different shapes when component is uniform → use a single skeleton in DataList
|
|
693
|
+
|
|
351
694
|
| Prop | Type | Default | Description |
|
|
352
695
|
|---|---|---|---|
|
|
353
696
|
| `as` | `'div'` \| `'span'` | `"div"` | Rendered tag |
|
|
@@ -384,7 +727,19 @@ Skeleton loading placeholder. Size/shape via className. With children, wraps tra
|
|
|
384
727
|
|
|
385
728
|
## Divider
|
|
386
729
|
|
|
387
|
-
Divider
|
|
730
|
+
Divider — visual separator (horizontal/vertical line).
|
|
731
|
+
|
|
732
|
+
WHEN TO USE:
|
|
733
|
+
• Separating sections within a card / list
|
|
734
|
+
• Vertical separator between inline items (orientation="vertical")
|
|
735
|
+
• Use color prop sparingly — defaults to border-default token
|
|
736
|
+
• For grouping form sections, prefer larger spacing over Divider
|
|
737
|
+
• Tab/Accordion already provide visual separation — don't add Divider
|
|
738
|
+
|
|
739
|
+
ANTI-PATTERNS:
|
|
740
|
+
✗ Stacking many Dividers — increase spacing instead
|
|
741
|
+
✗ Using Divider as decorative line with bright color → use Tailwind border utility on container
|
|
742
|
+
✗ Inline <hr> with custom CSS → Divider (consistent token)
|
|
388
743
|
|
|
389
744
|
| Prop | Type | Default | Description |
|
|
390
745
|
|---|---|---|---|
|
|
@@ -403,7 +758,20 @@ Divider. Supports horizontal/vertical, solid/dashed/dotted.
|
|
|
403
758
|
|
|
404
759
|
## Tooltip
|
|
405
760
|
|
|
406
|
-
Tooltip. Based on Radix Tooltip.
|
|
761
|
+
Tooltip — short text hint shown on hover/focus. Based on Radix Tooltip. Provider built-in.
|
|
762
|
+
|
|
763
|
+
WHEN TO USE:
|
|
764
|
+
• Brief explanation of an icon button or truncated label
|
|
765
|
+
• Hover-only, non-interactive content (text only)
|
|
766
|
+
• Need clickable content inside → Popover (not Tooltip)
|
|
767
|
+
• Action menu → DropdownMenu
|
|
768
|
+
• Force user attention → Modal
|
|
769
|
+
|
|
770
|
+
ANTI-PATTERNS:
|
|
771
|
+
✗ Buttons/links inside content → use Popover (Tooltip not focusable)
|
|
772
|
+
✗ Long paragraphs in tooltip → consider Popover or Drawer
|
|
773
|
+
✗ Tooltip on disabled buttons (won't show in some browsers) — wrap in span instead
|
|
774
|
+
✗ Critical info only in tooltip — duplicate visible elsewhere for mobile/touch
|
|
407
775
|
|
|
408
776
|
| Prop | Type | Default | Description |
|
|
409
777
|
|---|---|---|---|
|
|
@@ -427,7 +795,19 @@ Tooltip. Based on Radix Tooltip. Built-in Provider.
|
|
|
427
795
|
|
|
428
796
|
## Popover
|
|
429
797
|
|
|
430
|
-
Popover. Based on Radix Popover.
|
|
798
|
+
Popover — anchor-positioned panel with interactive content. Based on Radix Popover.
|
|
799
|
+
|
|
800
|
+
WHEN TO USE:
|
|
801
|
+
• Trigger-anchored UI with buttons, inputs, or rich content
|
|
802
|
+
• Filter/option panel near a button (not a full sidebar)
|
|
803
|
+
• Hover-only text hint → Tooltip (lighter)
|
|
804
|
+
• Action menu list → DropdownMenu (gives role=menu, keyboard nav)
|
|
805
|
+
• Decision dialog → Modal
|
|
806
|
+
|
|
807
|
+
ANTI-PATTERNS:
|
|
808
|
+
✗ Action lists with onClick handlers → DropdownMenu (a11y semantics)
|
|
809
|
+
✗ Implementing dropdown manually with Popover + custom buttons → DropdownMenu
|
|
810
|
+
✗ Long forms inside Popover → Drawer or Modal
|
|
431
811
|
|
|
432
812
|
| Prop | Type | Default | Description |
|
|
433
813
|
|---|---|---|---|
|
|
@@ -453,7 +833,21 @@ Popover. Based on Radix Popover.
|
|
|
453
833
|
|
|
454
834
|
## Accordion
|
|
455
835
|
|
|
456
|
-
Accordion
|
|
836
|
+
Accordion — collapsible content sections (FAQ, settings groups).
|
|
837
|
+
|
|
838
|
+
WHEN TO USE:
|
|
839
|
+
• FAQ, help docs, settings groups
|
|
840
|
+
• Long page where each section is independently scannable
|
|
841
|
+
• Hidden critical info — DO NOT bury what users always need
|
|
842
|
+
• Tab-like content switching → Tab (Accordion is for stacked, not exclusive)
|
|
843
|
+
|
|
844
|
+
type="single" + collapsible=true → all-closed allowed (recommended for FAQ).
|
|
845
|
+
type="multiple" → multiple sections open at once.
|
|
846
|
+
|
|
847
|
+
ANTI-PATTERNS:
|
|
848
|
+
✗ Hiding the page's primary content inside collapsed Accordion
|
|
849
|
+
✗ Single-section Accordion (just show the content)
|
|
850
|
+
✗ Custom toggle div + state — use Accordion (gets a11y, keyboard nav)
|
|
457
851
|
|
|
458
852
|
| Prop | Type | Default | Description |
|
|
459
853
|
|---|---|---|---|
|
|
@@ -484,7 +878,21 @@ Accordion. Supports both items array and composable patterns.
|
|
|
484
878
|
|
|
485
879
|
## Drawer
|
|
486
880
|
|
|
487
|
-
Drawer/bottom sheet. Based on Vaul. Compound component pattern.
|
|
881
|
+
Drawer / bottom sheet. Based on Vaul. Compound component pattern.
|
|
882
|
+
|
|
883
|
+
WHEN TO USE:
|
|
884
|
+
• Side panel for secondary action while user can still see main content (filter panel, item details)
|
|
885
|
+
• Mobile bottom sheet (direction="bottom")
|
|
886
|
+
• Force decision blocking main flow → Modal instead
|
|
887
|
+
• Inline anchor-positioned UI → Popover instead
|
|
888
|
+
|
|
889
|
+
DIRECTION: bottom (default, mobile-friendly), top, left, right.
|
|
890
|
+
dismissible=true (default) allows swipe/outside-click close. Set false for blocking flows.
|
|
891
|
+
|
|
892
|
+
ANTI-PATTERNS:
|
|
893
|
+
✗ Drawer for confirmation dialogs → Modal (decision-forcing)
|
|
894
|
+
✗ Always shouldScaleBackground → only enable for true mobile bottom-sheets
|
|
895
|
+
✗ Forgetting Drawer.Title → required for screen readers (a11y)
|
|
488
896
|
|
|
489
897
|
| Prop | Type | Default | Description |
|
|
490
898
|
|---|---|---|---|
|
|
@@ -530,6 +938,21 @@ Drawer.Content area.
|
|
|
530
938
|
|
|
531
939
|
Modal template. All modal components must be wrapped with ModalTemplate.
|
|
532
940
|
|
|
941
|
+
WHEN TO USE:
|
|
942
|
+
• Force user decision (delete confirm, submit confirm, blocking dialog)
|
|
943
|
+
• Long form/flow that needs full attention
|
|
944
|
+
• Side panel that doesn't block main flow → Drawer instead
|
|
945
|
+
• Inline contextual UI → Popover instead
|
|
946
|
+
• Short hint → Tooltip instead
|
|
947
|
+
|
|
948
|
+
PREFERRED API: use modal() / useModal() imperative API rather than mounting <ModalTemplate> directly. modal() handles stacking, focus return, ESC, and background scroll automatically.
|
|
949
|
+
|
|
950
|
+
ANTI-PATTERNS:
|
|
951
|
+
✗ Custom <div className="fixed inset-0"> → modal() (loses focus trap, a11y)
|
|
952
|
+
✗ Direct <ModalTemplate> mount in render tree → wrap with modal()
|
|
953
|
+
✗ Modal with no title for screen readers → always pass title prop
|
|
954
|
+
✗ Multiple modals stacking confusingly → use isAlone:true to close prior modals
|
|
955
|
+
|
|
533
956
|
| Prop | Type | Default | Description |
|
|
534
957
|
|---|---|---|---|
|
|
535
958
|
| `title` | `ReactNode` | - | Header title (ReactNode) |
|
|
@@ -627,7 +1050,22 @@ openModal({
|
|
|
627
1050
|
|
|
628
1051
|
## Tab
|
|
629
1052
|
|
|
630
|
-
Tab navigation
|
|
1053
|
+
Tab navigation — switch between content panels (settings sections, profile views).
|
|
1054
|
+
|
|
1055
|
+
WHEN TO USE:
|
|
1056
|
+
• Page area swap where only one panel is visible at a time
|
|
1057
|
+
• Mutually exclusive content with stable section labels
|
|
1058
|
+
• Form field selection → RadioGroup (semantics: not navigation)
|
|
1059
|
+
• Immediate filter/option toggle → ToggleGroup
|
|
1060
|
+
• Stacked collapsible sections → Accordion
|
|
1061
|
+
|
|
1062
|
+
destroyInactive=true unmounts hidden panels (saves memory but loses state).
|
|
1063
|
+
|
|
1064
|
+
ANTI-PATTERNS:
|
|
1065
|
+
✗ Tab with 1 item — just render the panel
|
|
1066
|
+
✗ Tab with 8+ items — consider sub-routing or DropdownMenu
|
|
1067
|
+
✗ Using Tab for form value selection → RadioGroup
|
|
1068
|
+
✗ Custom <button> + onClick + state → Tab (a11y, keyboard, focus management)
|
|
631
1069
|
|
|
632
1070
|
| Prop | Type | Default | Description |
|
|
633
1071
|
|---|---|---|---|
|
|
@@ -657,7 +1095,20 @@ Tab navigation. line/pill variants.
|
|
|
657
1095
|
|
|
658
1096
|
## Carousel
|
|
659
1097
|
|
|
660
|
-
Carousel. Based on Embla Carousel.
|
|
1098
|
+
Carousel — horizontal slide gallery. Based on Embla Carousel. Compound: CarouselSlide, CarouselPrev/Next, CarouselDots.
|
|
1099
|
+
|
|
1100
|
+
WHEN TO USE:
|
|
1101
|
+
• Hero banners, image gallery, product showcase
|
|
1102
|
+
• Multi-card horizontal scroll with snap behavior
|
|
1103
|
+
• Vertical scrolling list → Marquee or VirtualList (not Carousel)
|
|
1104
|
+
• Critical content that must always be visible — Carousel hides slides
|
|
1105
|
+
|
|
1106
|
+
opts={{ loop: true, align: 'start' }} for endless loop. Add Embla autoplay plugin for auto-advance.
|
|
1107
|
+
|
|
1108
|
+
ANTI-PATTERNS:
|
|
1109
|
+
✗ Critical CTA inside non-first slide (users might never scroll)
|
|
1110
|
+
✗ Auto-advance without pause-on-hover (a11y)
|
|
1111
|
+
✗ Manual scroll-snap div → Carousel (gets keyboard nav, indicators)
|
|
661
1112
|
|
|
662
1113
|
| Prop | Type | Default | Description |
|
|
663
1114
|
|---|---|---|---|
|
|
@@ -683,7 +1134,19 @@ Sub-components: `CarouselSlide`, `CarouselPrev`, `CarouselNext`, `CarouselDots`
|
|
|
683
1134
|
|
|
684
1135
|
## Pagination
|
|
685
1136
|
|
|
686
|
-
Pagination
|
|
1137
|
+
Pagination — page-by-page navigation for long datasets.
|
|
1138
|
+
|
|
1139
|
+
WHEN TO USE:
|
|
1140
|
+
• User needs to jump to specific pages (search results, blog archives)
|
|
1141
|
+
• Total count is known and stable
|
|
1142
|
+
• Continuous browsing → InfiniteScroll instead
|
|
1143
|
+
• Real-time stream → InfiniteScroll
|
|
1144
|
+
• Both fit → Pagination is more accessible (keyboard, deep-linkable URL)
|
|
1145
|
+
|
|
1146
|
+
ANTI-PATTERNS:
|
|
1147
|
+
✗ Pagination with totalPages=1 — hide it
|
|
1148
|
+
✗ Mixing Pagination + InfiniteScroll on same list (confusing)
|
|
1149
|
+
✗ Custom page button divs → Pagination (a11y, edge cases like ellipsis)
|
|
687
1150
|
|
|
688
1151
|
| Prop | Type | Default | Description |
|
|
689
1152
|
|---|---|---|---|
|
|
@@ -703,7 +1166,20 @@ Pagination. Previous/next + page number buttons.
|
|
|
703
1166
|
|
|
704
1167
|
## Avatar
|
|
705
1168
|
|
|
706
|
-
Avatar
|
|
1169
|
+
Avatar — user/entity profile image with text fallback.
|
|
1170
|
+
|
|
1171
|
+
WHEN TO USE:
|
|
1172
|
+
• User profiles, comment authors, team member lists, message sender icons
|
|
1173
|
+
• Image fails / not provided → fallback (initials or icon) shown automatically
|
|
1174
|
+
• Need optimized image (Next.js) → pass <Image> via children, omit src
|
|
1175
|
+
• Larger illustration / logo → use NxImage instead
|
|
1176
|
+
|
|
1177
|
+
shape="square" for organization/team logos; "circle" for people (default).
|
|
1178
|
+
|
|
1179
|
+
ANTI-PATTERNS:
|
|
1180
|
+
✗ <img> + manual fallback handling → Avatar (handles error)
|
|
1181
|
+
✗ Avatar without alt for screen readers (always set alt or aria-label)
|
|
1182
|
+
✗ Mixing avatar sizes inconsistently in a list — pick one size
|
|
707
1183
|
|
|
708
1184
|
| Prop | Type | Default | Description |
|
|
709
1185
|
|---|---|---|---|
|
|
@@ -725,7 +1201,20 @@ Avatar. Supports image, fallback text, and children.
|
|
|
725
1201
|
|
|
726
1202
|
## Counter
|
|
727
1203
|
|
|
728
|
-
|
|
1204
|
+
Counter — animated number tick from startValue to endValue.
|
|
1205
|
+
|
|
1206
|
+
WHEN TO USE:
|
|
1207
|
+
• Marketing landing — "10,000+ users" KPI displays
|
|
1208
|
+
• Stat dashboards (animate when value changes)
|
|
1209
|
+
• Live tickers / real-time data — re-render plain text instead (Counter is one-shot animation)
|
|
1210
|
+
• Currency input / editable number → NumberInput / PriceInput
|
|
1211
|
+
|
|
1212
|
+
triggerOnView=true delays animation until element scrolls into view (good for landing pages).
|
|
1213
|
+
|
|
1214
|
+
ANTI-PATTERNS:
|
|
1215
|
+
✗ Counter for values that update frequently — animation queue conflicts
|
|
1216
|
+
✗ Long duration on critical numbers (users wait to read)
|
|
1217
|
+
✗ Counter without separator for large numbers (hard to read)
|
|
729
1218
|
|
|
730
1219
|
| Prop | Type | Default | Description |
|
|
731
1220
|
|---|---|---|---|
|
|
@@ -748,7 +1237,20 @@ Number count animation.
|
|
|
748
1237
|
|
|
749
1238
|
## Countdown
|
|
750
1239
|
|
|
751
|
-
Countdown timer.
|
|
1240
|
+
Countdown — live timer counting down to endTimestamp (Unix ms).
|
|
1241
|
+
|
|
1242
|
+
WHEN TO USE:
|
|
1243
|
+
• Sale ends in / event starts in / token claim window
|
|
1244
|
+
• OTP / verification code expiry
|
|
1245
|
+
• Counting up (since X) → not Countdown; use Counter or custom interval
|
|
1246
|
+
• Persistent server time → pass server-synced endTimestamp (not Date.now offset)
|
|
1247
|
+
|
|
1248
|
+
showDays=false for short timers (<24h) to declutter. Use render prop for fully custom layouts.
|
|
1249
|
+
|
|
1250
|
+
ANTI-PATTERNS:
|
|
1251
|
+
✗ Countdown without onEnd handler — UI stuck at 0
|
|
1252
|
+
✗ Countdown across SSR without hydration safety — pass endTimestamp from server
|
|
1253
|
+
✗ Updating endTimestamp every render — causes jitter
|
|
752
1254
|
|
|
753
1255
|
| Prop | Type | Default | Description |
|
|
754
1256
|
|---|---|---|---|
|
|
@@ -768,7 +1270,20 @@ Countdown timer.
|
|
|
768
1270
|
|
|
769
1271
|
## Marquee
|
|
770
1272
|
|
|
771
|
-
Marquee
|
|
1273
|
+
Marquee — endless scrolling content (logo strip, promo banner, news ticker).
|
|
1274
|
+
|
|
1275
|
+
WHEN TO USE:
|
|
1276
|
+
• Logo cloud, partner brands strip, social proof
|
|
1277
|
+
• Critical info that must be read → not Marquee (auto-scroll hurts a11y)
|
|
1278
|
+
• Long static list → VirtualList
|
|
1279
|
+
• User-paced gallery → Carousel
|
|
1280
|
+
|
|
1281
|
+
pauseOnHover=true is recommended whenever content is readable text.
|
|
1282
|
+
|
|
1283
|
+
ANTI-PATTERNS:
|
|
1284
|
+
✗ Marquee with action buttons inside (hard to click)
|
|
1285
|
+
✗ Fast-speed marquee on important text (unreadable, a11y violation)
|
|
1286
|
+
✗ Multiple Marquees on same page in different directions (visual chaos)
|
|
772
1287
|
|
|
773
1288
|
| Prop | Type | Default | Description |
|
|
774
1289
|
|---|---|---|---|
|
|
@@ -790,7 +1305,21 @@ Marquee (scrolling text/elements).
|
|
|
790
1305
|
|
|
791
1306
|
## VirtualList
|
|
792
1307
|
|
|
793
|
-
|
|
1308
|
+
VirtualList — performant rendering of huge lists (1000+ items). Based on @tanstack/react-virtual.
|
|
1309
|
+
|
|
1310
|
+
WHEN TO USE:
|
|
1311
|
+
• Large datasets (>200 rows): chat history, transactions, logs, leaderboard
|
|
1312
|
+
• Stable item height (or measurable per-item via estimateSize fn)
|
|
1313
|
+
• Small list (<100 items) → DataList (much simpler API)
|
|
1314
|
+
• Grid layout → VirtualGrid
|
|
1315
|
+
• Server-side pagination → InfiniteScroll wrapping plain list
|
|
1316
|
+
|
|
1317
|
+
estimateSize is critical — wrong values cause scroll jumps. Pair with onEndReached for server pagination.
|
|
1318
|
+
|
|
1319
|
+
ANTI-PATTERNS:
|
|
1320
|
+
✗ VirtualList for short lists (DataList is simpler and renders faster)
|
|
1321
|
+
✗ Variable estimateSize for uniform items — use a single number
|
|
1322
|
+
✗ Putting interactive overlays inside virtual rows (mounted/unmounted on scroll)
|
|
794
1323
|
|
|
795
1324
|
| Prop | Type | Default | Description |
|
|
796
1325
|
|---|---|---|---|
|
|
@@ -806,7 +1335,18 @@ Virtual scroll list. Based on @tanstack/react-virtual.
|
|
|
806
1335
|
|
|
807
1336
|
### VirtualGrid
|
|
808
1337
|
|
|
809
|
-
|
|
1338
|
+
VirtualGrid — performant grid for huge lists. Based on @tanstack/react-virtual.
|
|
1339
|
+
|
|
1340
|
+
WHEN TO USE:
|
|
1341
|
+
• Image gallery / card grid with 200+ items
|
|
1342
|
+
• Fixed column count (responsive via JS — recompute columns on breakpoint)
|
|
1343
|
+
• Small grid → DataGrid (no virtualization, simpler API)
|
|
1344
|
+
• Single-column list → VirtualList
|
|
1345
|
+
|
|
1346
|
+
ANTI-PATTERNS:
|
|
1347
|
+
✗ VirtualGrid with <50 items — DataGrid is simpler
|
|
1348
|
+
✗ Cards with hover-expanding height (breaks virtualization)
|
|
1349
|
+
✗ Forgetting to recompute columns on resize → visible empty space
|
|
810
1350
|
|
|
811
1351
|
| Prop | Type | Default | Description |
|
|
812
1352
|
|---|---|---|---|
|
|
@@ -842,7 +1382,21 @@ Same as VirtualList + `columns: number` (required).
|
|
|
842
1382
|
|
|
843
1383
|
## DataList
|
|
844
1384
|
|
|
845
|
-
|
|
1385
|
+
DataList — render-prop list that handles loading / skeleton / empty / error / data in one component. Built-in ErrorBoundary.
|
|
1386
|
+
|
|
1387
|
+
WHEN TO USE:
|
|
1388
|
+
• Any async list (<200 items) — feed, comments, dashboard rows
|
|
1389
|
+
• Pass list={null} during loading (auto-shows skeleton/spinner)
|
|
1390
|
+
• Pass list=[] for empty state (auto-shows noDataMessage)
|
|
1391
|
+
• Card grid → DataGrid (same API + columns prop)
|
|
1392
|
+
• Huge dataset → VirtualList
|
|
1393
|
+
|
|
1394
|
+
children is render fn: ({ item, index }) => ReactNode.
|
|
1395
|
+
|
|
1396
|
+
ANTI-PATTERNS:
|
|
1397
|
+
✗ Manual if (loading) {...} else if (!data.length) {...} chains → DataList handles all
|
|
1398
|
+
✗ DataList without skeletonElement when component shape is known (use Skeleton)
|
|
1399
|
+
✗ list=undefined (treated as data, not loading) — use null for loading
|
|
846
1400
|
|
|
847
1401
|
| Prop | Type | Default | Description |
|
|
848
1402
|
|---|---|---|---|
|
|
@@ -891,9 +1445,104 @@ function UserSkeleton() {
|
|
|
891
1445
|
|
|
892
1446
|
---
|
|
893
1447
|
|
|
1448
|
+
## DataGrid
|
|
1449
|
+
|
|
1450
|
+
DataGrid — card grid version of DataList with responsive columns. Built-in ErrorBoundary, loading/skeleton/empty/error states.
|
|
1451
|
+
|
|
1452
|
+
WHEN TO USE:
|
|
1453
|
+
• Card grids: products, gallery, team members, posts
|
|
1454
|
+
• Need responsive column count → pass { base: 1, sm: 2, lg: 3, xl: 4 }
|
|
1455
|
+
• Single column / row layout → DataList
|
|
1456
|
+
• Tabular data → table component (not DataGrid)
|
|
1457
|
+
• Huge dataset → VirtualGrid
|
|
1458
|
+
|
|
1459
|
+
ANTI-PATTERNS:
|
|
1460
|
+
✗ Tabular data forced into card grid (use a table)
|
|
1461
|
+
✗ Hardcoded column count for responsive layouts → use responsive object
|
|
1462
|
+
✗ Manual loading/empty handling → DataGrid does it
|
|
1463
|
+
|
|
1464
|
+
| Prop | Type | Default | Description |
|
|
1465
|
+
|---|---|---|---|
|
|
1466
|
+
| `list` | `ReactNode`[] | - | Data array to render. null = loading state (required) |
|
|
1467
|
+
| `columns` | `number` \| `object` | - | Column count. number = fixed | { base, sm, md, lg, xl, 2xl } = responsive (required) |
|
|
1468
|
+
| `gap` | `number` \| `string` | - | Item gap. number = px | string = CSS value |
|
|
1469
|
+
| `noDataMessage` | `ReactNode` | - | Message for empty array (string | ReactElement) |
|
|
1470
|
+
| `errorFallback` | `ReactNode` | - | Fallback on error (ReactNode) |
|
|
1471
|
+
| `loadingElement` | `ReactNode` | - | Custom loading element (default: Spinner) |
|
|
1472
|
+
| `skeletonElement` | `ReactNode` | - | Skeleton element during loading (ReactElement) |
|
|
1473
|
+
| `skeletonCount` | `number` | `3` | Skeleton repeat count |
|
|
1474
|
+
| `loading` | `boolean` | `false` | Force loading state |
|
|
1475
|
+
| `children` | `ReactNode` | - | Item render function: ({ item, index }) => ReactNode (required) |
|
|
1476
|
+
| `className` | `string` | - | Root element style |
|
|
1477
|
+
|
|
1478
|
+
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.
|
|
1479
|
+
|
|
1480
|
+
- **`columns`**: `number` for fixed, or `{ base, sm, md, lg, xl, 2xl }` for responsive (mobile-first; missing breakpoints inherit from the previous one).
|
|
1481
|
+
- **`gap`**: `number` (px) or any CSS gap value (e.g. `'1rem'`).
|
|
1482
|
+
- All other props (`list`, `skeletonElement`, `noDataMessage`, `errorFallback`, `loading`) behave exactly like `DataList`.
|
|
1483
|
+
|
|
1484
|
+
```tsx
|
|
1485
|
+
// 1) Fixed columns
|
|
1486
|
+
<DataGrid list={projects} columns={3} gap={12}>
|
|
1487
|
+
{({ item }) => <ProjectCard key={item.id} project={item} />}
|
|
1488
|
+
</DataGrid>
|
|
1489
|
+
|
|
1490
|
+
// 2) Responsive columns (mobile-first)
|
|
1491
|
+
<DataGrid
|
|
1492
|
+
list={projects}
|
|
1493
|
+
columns={{ base: 1, sm: 2, lg: 3, xl: 4 }}
|
|
1494
|
+
gap={12}
|
|
1495
|
+
>
|
|
1496
|
+
{({ item }) => <ProjectCard key={item.id} project={item} />}
|
|
1497
|
+
</DataGrid>
|
|
1498
|
+
|
|
1499
|
+
// 3) Skeleton loading (auto-shown when list is null)
|
|
1500
|
+
<DataGrid
|
|
1501
|
+
list={projects}
|
|
1502
|
+
columns={{ base: 1, md: 2, lg: 3 }}
|
|
1503
|
+
gap={12}
|
|
1504
|
+
skeletonElement={<ProjectCardSkeleton />}
|
|
1505
|
+
skeletonCount={6}
|
|
1506
|
+
>
|
|
1507
|
+
{({ item }) => <ProjectCard key={item.id} project={item} />}
|
|
1508
|
+
</DataGrid>
|
|
1509
|
+
|
|
1510
|
+
// 4) Empty / error states
|
|
1511
|
+
<DataGrid
|
|
1512
|
+
list={projects}
|
|
1513
|
+
columns={3}
|
|
1514
|
+
gap={12}
|
|
1515
|
+
noDataMessage="No projects"
|
|
1516
|
+
errorFallback={<div>Failed to load</div>}
|
|
1517
|
+
>
|
|
1518
|
+
{({ item }) => <ProjectCard key={item.id} project={item} />}
|
|
1519
|
+
</DataGrid>
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
**When to use `Table` vs `DataGrid`**:
|
|
1523
|
+
- Tabular data with rows/columns → `Table` + `TableRow` + `TdColumn`
|
|
1524
|
+
- Card-style items in a responsive grid → `DataGrid`
|
|
1525
|
+
- Either view of the same data (toggle pattern) → both, switched by a state-driven `ToggleGroup`
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
894
1529
|
## InfiniteScroll
|
|
895
1530
|
|
|
896
|
-
|
|
1531
|
+
InfiniteScroll — auto-load more when sentinel enters viewport. Based on IntersectionObserver.
|
|
1532
|
+
|
|
1533
|
+
WHEN TO USE:
|
|
1534
|
+
• Feeds, social timelines, search-as-you-scroll
|
|
1535
|
+
• Continuous browsing where total isn't critical
|
|
1536
|
+
• Need jump-to-page → Pagination (not InfiniteScroll)
|
|
1537
|
+
• Need to render 1000s of already-loaded items efficiently → VirtualList
|
|
1538
|
+
• Combine with VirtualList for huge + paginated data
|
|
1539
|
+
|
|
1540
|
+
Pass either totalCount or hasMore (not both). handleLoadMore is required.
|
|
1541
|
+
|
|
1542
|
+
ANTI-PATTERNS:
|
|
1543
|
+
✗ InfiniteScroll without footer/loading element (looks broken at the bottom)
|
|
1544
|
+
✗ InfiniteScroll without debouncing handleLoadMore — duplicate fetches
|
|
1545
|
+
✗ Auto-loading critical actions in footer (links, contact) — they become unreachable
|
|
897
1546
|
|
|
898
1547
|
| Prop | Type | Default | Description |
|
|
899
1548
|
|---|---|---|---|
|
|
@@ -924,7 +1573,20 @@ Infinite scroll. Based on IntersectionObserver.
|
|
|
924
1573
|
|
|
925
1574
|
## Ellipsis
|
|
926
1575
|
|
|
927
|
-
|
|
1576
|
+
Ellipsis — clamp long text to N lines with "show more / less" toggle.
|
|
1577
|
+
|
|
1578
|
+
WHEN TO USE:
|
|
1579
|
+
• Long descriptions in cards, comments, post previews
|
|
1580
|
+
• Single-line truncation only → CSS line-clamp utility (lighter)
|
|
1581
|
+
• Tooltip on hover for full text → Tooltip + truncate utility
|
|
1582
|
+
• Critical content that must be fully readable — don't truncate
|
|
1583
|
+
|
|
1584
|
+
triggerMore/triggerLess accept ReactNode for icons or styled buttons.
|
|
1585
|
+
|
|
1586
|
+
ANTI-PATTERNS:
|
|
1587
|
+
✗ Ellipsis on titles / labels — confusing UX
|
|
1588
|
+
✗ Always-collapsed (defaultShortened=true) for short text — measurement waste
|
|
1589
|
+
✗ Overriding triggerMore/Less with HTML that breaks accessibility (use button-like)
|
|
928
1590
|
|
|
929
1591
|
| Prop | Type | Default | Description |
|
|
930
1592
|
|---|---|---|---|
|
|
@@ -947,6 +1609,16 @@ Text ellipsis. Built-in show more/less toggle.
|
|
|
947
1609
|
|
|
948
1610
|
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
1611
|
|
|
1612
|
+
WHEN TO USE:
|
|
1613
|
+
• Any numeric field — quantity, score, age, count
|
|
1614
|
+
• Currency with thousand separators → PriceInput instead
|
|
1615
|
+
• Date / time → DatePicker instead
|
|
1616
|
+
|
|
1617
|
+
ANTI-PATTERNS:
|
|
1618
|
+
✗ <TextInput type="number"> → <NumberInput> (loses keyboard ↑↓, step, accelerated long-press, max click)
|
|
1619
|
+
✗ Custom +/- buttons + <input> → variant="bind" (or numberInputBind for external)
|
|
1620
|
+
✗ Manual thousand separators for currency → PriceInput
|
|
1621
|
+
|
|
950
1622
|
| Prop | Type | Default | Description |
|
|
951
1623
|
|---|---|---|---|
|
|
952
1624
|
| `variant` | `'basic'` \| `'bind'` | `"basic"` | Variant: basic (right chevron arrows) or bind (left/right +/- buttons) |
|
|
@@ -1001,7 +1673,20 @@ const ref = useRef<NumberInputRef>(null);
|
|
|
1001
1673
|
|
|
1002
1674
|
## PriceInput
|
|
1003
1675
|
|
|
1004
|
-
|
|
1676
|
+
PriceInput — currency / amount input with prefix, suffix, balance display, click-to-fill.
|
|
1677
|
+
|
|
1678
|
+
WHEN TO USE:
|
|
1679
|
+
• Money / token amounts: payment, transfer, swap, withdraw
|
|
1680
|
+
• Need balance hint with auto-fill UX (set balance + onBalanceClick)
|
|
1681
|
+
• Pure number (count, age, score) → NumberInput
|
|
1682
|
+
• Generic text → TextInput
|
|
1683
|
+
|
|
1684
|
+
separator=true displays commas; onValueChange always returns raw value (no commas). maxBalance auto-applies error styling on overflow.
|
|
1685
|
+
|
|
1686
|
+
ANTI-PATTERNS:
|
|
1687
|
+
✗ <TextInput type="number"> + manual currency formatting → PriceInput
|
|
1688
|
+
✗ NumberInput for currency (loses prefix/suffix/balance UX)
|
|
1689
|
+
✗ Using string input for amounts then parseFloat — lose precision; use this component
|
|
1005
1690
|
|
|
1006
1691
|
| Prop | Type | Default | Description |
|
|
1007
1692
|
|---|---|---|---|
|
|
@@ -1029,7 +1714,21 @@ Price/amount input field with prefix, suffix, balance display, and auto-fill on
|
|
|
1029
1714
|
|
|
1030
1715
|
## Badge
|
|
1031
1716
|
|
|
1032
|
-
Badge
|
|
1717
|
+
Badge — small status / count indicator overlaid on an anchor (icon, avatar, button).
|
|
1718
|
+
|
|
1719
|
+
WHEN TO USE:
|
|
1720
|
+
• Notification count on bell icon
|
|
1721
|
+
• Unread message count on inbox tab
|
|
1722
|
+
• Status dot (online/offline) → dot=true
|
|
1723
|
+
• Status label with text (e.g. "PRO", "NEW") → Chip variant="accent"
|
|
1724
|
+
• Removable filter token → Chip with onClose
|
|
1725
|
+
|
|
1726
|
+
count={0} hides badge by default; pass showZero=true to keep visible.
|
|
1727
|
+
|
|
1728
|
+
ANTI-PATTERNS:
|
|
1729
|
+
✗ Using Badge as standalone label without anchor → Chip
|
|
1730
|
+
✗ count > 999 without max → ugly layout; default max=99 ("99+")
|
|
1731
|
+
✗ Multiple badges on same anchor (visual noise)
|
|
1033
1732
|
|
|
1034
1733
|
| Prop | Type | Default | Description |
|
|
1035
1734
|
|---|---|---|---|
|
|
@@ -1047,7 +1746,21 @@ Badge indicator. Dot or count display. Wraps children when provided.
|
|
|
1047
1746
|
|
|
1048
1747
|
## Progress
|
|
1049
1748
|
|
|
1050
|
-
Progress
|
|
1749
|
+
Progress — linear progress bar (file upload, multi-step form, loading with known %).
|
|
1750
|
+
|
|
1751
|
+
WHEN TO USE:
|
|
1752
|
+
• Quantifiable progress (% complete, X of Y)
|
|
1753
|
+
• Unknown duration spinner → Spinner
|
|
1754
|
+
• Stable component shape during load → Skeleton
|
|
1755
|
+
• Step-based navigation → Stepper
|
|
1756
|
+
• Indeterminate (loading without %) → set indeterminate=true
|
|
1757
|
+
|
|
1758
|
+
variant follows semantic colors (success when complete, danger on error).
|
|
1759
|
+
|
|
1760
|
+
ANTI-PATTERNS:
|
|
1761
|
+
✗ Indeterminate Progress for short loads (<1s) — Spinner is lighter
|
|
1762
|
+
✗ Custom <div style={{ width: x% }}> → Progress (a11y, tokens)
|
|
1763
|
+
✗ Progress without label/showValue when % is critical info
|
|
1051
1764
|
|
|
1052
1765
|
| Prop | Type | Default | Description |
|
|
1053
1766
|
|---|---|---|---|
|
|
@@ -1064,7 +1777,20 @@ Progress bar. Linear progress indicator with percentage display.
|
|
|
1064
1777
|
|
|
1065
1778
|
## Alert
|
|
1066
1779
|
|
|
1067
|
-
Alert
|
|
1780
|
+
Alert — persistent inline notification (banner). Auto icon by variant.
|
|
1781
|
+
|
|
1782
|
+
WHEN TO USE:
|
|
1783
|
+
• In-page status: form errors, server warnings, info banners, success confirmation
|
|
1784
|
+
• Transient toast/snackbar → use a toast library, NOT Alert
|
|
1785
|
+
• Modal-blocking error → Modal with semantic="danger"
|
|
1786
|
+
• Inline form field error → use error/description prop on TextInput / Select / etc.
|
|
1787
|
+
|
|
1788
|
+
variant maps to semantic colors. closable=true gives users dismiss control. action prop reserved for inline buttons (e.g. "Retry").
|
|
1789
|
+
|
|
1790
|
+
ANTI-PATTERNS:
|
|
1791
|
+
✗ Stacking 5 alerts at top of page (use one with summary)
|
|
1792
|
+
✗ Critical destructive action confirmation in Alert → Modal
|
|
1793
|
+
✗ Wrapping <div className="bg-red-100"> manually → Alert (a11y role + tokens)
|
|
1068
1794
|
|
|
1069
1795
|
| Prop | Type | Default | Description |
|
|
1070
1796
|
|---|---|---|---|
|
|
@@ -1081,7 +1807,20 @@ Alert / Banner. Inline notification with icon, title, description.
|
|
|
1081
1807
|
|
|
1082
1808
|
## EmptyState
|
|
1083
1809
|
|
|
1084
|
-
|
|
1810
|
+
EmptyState — friendly placeholder for empty lists, no search results, first-time setup.
|
|
1811
|
+
|
|
1812
|
+
WHEN TO USE:
|
|
1813
|
+
• Empty inbox / list / search results
|
|
1814
|
+
• First-time use (onboarding nudge with action button)
|
|
1815
|
+
• Loading → Skeleton/Spinner (NOT EmptyState)
|
|
1816
|
+
• Error → Alert (or pass icon + title to EmptyState only when conceptually "nothing here")
|
|
1817
|
+
|
|
1818
|
+
DataList/DataGrid have built-in noDataMessage that wraps EmptyState — use that prop instead of conditional rendering.
|
|
1819
|
+
|
|
1820
|
+
ANTI-PATTERNS:
|
|
1821
|
+
✗ Showing EmptyState during loading (confuses users into thinking data is missing)
|
|
1822
|
+
✗ EmptyState without action when user can fix it ("Create your first X" button)
|
|
1823
|
+
✗ Cluttered EmptyState (too much text/multiple CTAs) — keep one primary action
|
|
1085
1824
|
|
|
1086
1825
|
| Prop | Type | Default | Description |
|
|
1087
1826
|
|---|---|---|---|
|
|
@@ -1097,7 +1836,21 @@ Empty state placeholder. Shown when data is empty or unavailable.
|
|
|
1097
1836
|
|
|
1098
1837
|
## Breadcrumb
|
|
1099
1838
|
|
|
1100
|
-
Breadcrumb
|
|
1839
|
+
Breadcrumb — hierarchical location navigation (Home > Settings > Profile).
|
|
1840
|
+
|
|
1841
|
+
WHEN TO USE:
|
|
1842
|
+
• Multi-level information architecture (>=3 levels deep)
|
|
1843
|
+
• Hierarchical category drill-down (file system, taxonomy)
|
|
1844
|
+
• Step-based linear flow → Stepper (not Breadcrumb)
|
|
1845
|
+
• Tab switching → Tab
|
|
1846
|
+
• Single-level navigation → page title alone
|
|
1847
|
+
|
|
1848
|
+
Compound pattern: use <Breadcrumb.Item> children. Each Item wraps any ReactNode (Link, Select, plain text). Use maxItems for long paths (auto-collapse with "…").
|
|
1849
|
+
|
|
1850
|
+
ANTI-PATTERNS:
|
|
1851
|
+
✗ Breadcrumb with 1 level — just show page title
|
|
1852
|
+
✗ Breadcrumb with > 6 visible items → set maxItems
|
|
1853
|
+
✗ Last item as a link (it's the current page; should be plain text)
|
|
1101
1854
|
|
|
1102
1855
|
| Prop | Type | Default | Description |
|
|
1103
1856
|
|---|---|---|---|
|
|
@@ -1110,7 +1863,21 @@ Breadcrumb navigation (compound component pattern). Use <Breadcrumb.Item> childr
|
|
|
1110
1863
|
|
|
1111
1864
|
## Stepper
|
|
1112
1865
|
|
|
1113
|
-
Stepper
|
|
1866
|
+
Stepper — multi-step linear flow indicator (checkout, onboarding, wizard).
|
|
1867
|
+
|
|
1868
|
+
WHEN TO USE:
|
|
1869
|
+
• Sequential workflow with finite steps (3-7)
|
|
1870
|
+
• Show user's current position in flow + completed/upcoming steps
|
|
1871
|
+
• Independent navigation between unrelated sections → Tab
|
|
1872
|
+
• Continuous % progress → Progress
|
|
1873
|
+
• Hierarchy / location → Breadcrumb
|
|
1874
|
+
|
|
1875
|
+
status="error" highlights the current step with danger color when validation fails.
|
|
1876
|
+
|
|
1877
|
+
ANTI-PATTERNS:
|
|
1878
|
+
✗ Stepper with 2 steps (use Progress or just navigation)
|
|
1879
|
+
✗ Stepper with 10+ steps (overwhelming) — chunk into sub-flows
|
|
1880
|
+
✗ Letting users skip ahead by clicking future steps (only mark completed → current is allowed)
|
|
1114
1881
|
|
|
1115
1882
|
| Prop | Type | Default | Description |
|
|
1116
1883
|
|---|---|---|---|
|
|
@@ -1125,7 +1892,22 @@ Stepper. Step-by-step progress indicator.
|
|
|
1125
1892
|
|
|
1126
1893
|
## DropdownMenu
|
|
1127
1894
|
|
|
1128
|
-
Dropdown menu. Based on Radix DropdownMenu
|
|
1895
|
+
Dropdown menu — list of actions/commands triggered by a button. Based on Radix DropdownMenu (role="menu" + keyboard nav).
|
|
1896
|
+
|
|
1897
|
+
WHEN TO USE:
|
|
1898
|
+
• "More" / context menu (•••, ⋮)
|
|
1899
|
+
• Action lists where each item triggers a function (save, share, delete)
|
|
1900
|
+
• Selecting a value (form field) → Select / Combobox (not DropdownMenu)
|
|
1901
|
+
• Multi-select toggles → ToggleGroup or CheckBox group
|
|
1902
|
+
• Anchored info panel → Popover
|
|
1903
|
+
|
|
1904
|
+
Use danger=true on destructive items (Delete) for visual + semantic emphasis.
|
|
1905
|
+
Use separator=true to group related actions.
|
|
1906
|
+
|
|
1907
|
+
ANTI-PATTERNS:
|
|
1908
|
+
✗ DropdownMenu for value selection submitted later → Select / Combobox
|
|
1909
|
+
✗ Custom <div onClick> with isOpen state → DropdownMenu
|
|
1910
|
+
✗ Long form inputs inside menu items → Popover instead
|
|
1129
1911
|
|
|
1130
1912
|
| Prop | Type | Default | Description |
|
|
1131
1913
|
|---|---|---|---|
|
|
@@ -1140,7 +1922,27 @@ Dropdown menu. Based on Radix DropdownMenu. Action menu for context/more menus.
|
|
|
1140
1922
|
|
|
1141
1923
|
## ToggleGroup
|
|
1142
1924
|
|
|
1143
|
-
Toggle group / Segment control. Based on Radix ToggleGroup.
|
|
1925
|
+
Toggle group / Segment control for immediate-effect option selection. Based on Radix ToggleGroup.
|
|
1926
|
+
|
|
1927
|
+
WHEN TO USE:
|
|
1928
|
+
• Sort order, view mode (grid/list), filter chips — selection takes effect immediately
|
|
1929
|
+
• Visual comparison important (icons or short labels)
|
|
1930
|
+
• Form field that submits later → RadioGroup or CheckBox instead
|
|
1931
|
+
• Page area switching (settings tabs) → Tab instead
|
|
1932
|
+
|
|
1933
|
+
VARIANTS:
|
|
1934
|
+
• default — slider-style background (sleek)
|
|
1935
|
+
• primary / secondary — accent-filled selected state
|
|
1936
|
+
• outline — bordered buttons with no background (button-style toolbar)
|
|
1937
|
+
|
|
1938
|
+
fullWidth=true distributes items equally across parent width (use with outline variant for button-row look).
|
|
1939
|
+
|
|
1940
|
+
type="single" requires at least one selection by default (required=true). Set required=false to allow deselection.
|
|
1941
|
+
|
|
1942
|
+
ANTI-PATTERNS:
|
|
1943
|
+
✗ Using ToggleGroup for content tabs → Tab
|
|
1944
|
+
✗ Using ToggleGroup as form field submitted later → RadioGroup (semantics + a11y)
|
|
1945
|
+
✗ Mixing icons and text labels of different sizes — keep visual rhythm consistent
|
|
1144
1946
|
|
|
1145
1947
|
| Prop | Type | Default | Description |
|
|
1146
1948
|
|---|---|---|---|
|
|
@@ -1149,16 +1951,32 @@ Toggle group / Segment control. Based on Radix ToggleGroup.
|
|
|
1149
1951
|
| `value` | `ReactNode` | - | Controlled value (string | string[]) |
|
|
1150
1952
|
| `defaultValue` | `ReactNode` | - | Default value |
|
|
1151
1953
|
| `onValueChange` | `ReactNode` | - | Value change callback |
|
|
1152
|
-
| `variant` | `'default'` \| `'outline'` | `"default"` | Style variant |
|
|
1954
|
+
| `variant` | `'default'` \| `'primary'` \| `'secondary'` \| `'outline'` | `"default"` | Style variant (default=slider, primary/secondary=accent filled, outline=bordered buttons with no background) |
|
|
1153
1955
|
| `size` | `'sm'` \| `'md'` \| `'lg'` | `"md"` | Size |
|
|
1956
|
+
| `fullWidth` | `boolean` | `false` | Stretch the group to parent width and split items equally |
|
|
1154
1957
|
| `disabled` | `boolean` | - | Disable all items |
|
|
1958
|
+
| `required` | `boolean` | `true` | Prevent deselection (at least one must be selected) |
|
|
1155
1959
|
| `className` | `string` | - | Style override |
|
|
1156
1960
|
|
|
1157
1961
|
---
|
|
1158
1962
|
|
|
1159
1963
|
## Slider
|
|
1160
1964
|
|
|
1161
|
-
Slider
|
|
1965
|
+
Slider — numeric range selector. Based on Radix Slider. Single thumb or range (two thumbs).
|
|
1966
|
+
|
|
1967
|
+
WHEN TO USE:
|
|
1968
|
+
• Continuous range with visual feedback: volume, brightness, price filter
|
|
1969
|
+
• Range filter (min-max) → defaultValue=[20, 80]
|
|
1970
|
+
• Precise number entry → NumberInput
|
|
1971
|
+
• Discrete few options → ToggleGroup or RadioGroup
|
|
1972
|
+
• Boolean → Switch
|
|
1973
|
+
|
|
1974
|
+
step controls increments. onValueCommit fires only after pointer release (good for expensive recomputations).
|
|
1975
|
+
|
|
1976
|
+
ANTI-PATTERNS:
|
|
1977
|
+
✗ Slider for required precise input (typing 47 is faster than dragging)
|
|
1978
|
+
✗ Long step count without showValue / labels (users have no reference)
|
|
1979
|
+
✗ Calling expensive API on every onValueChange tick → use onValueCommit
|
|
1162
1980
|
|
|
1163
1981
|
| Prop | Type | Default | Description |
|
|
1164
1982
|
|---|---|---|---|
|
|
@@ -1180,7 +1998,20 @@ Slider / Range input. Based on Radix Slider. Supports single and range mode.
|
|
|
1180
1998
|
|
|
1181
1999
|
## TagInput
|
|
1182
2000
|
|
|
1183
|
-
|
|
2001
|
+
TagInput — free-form multi-value entry (Enter to add, Backspace to remove last).
|
|
2002
|
+
|
|
2003
|
+
WHEN TO USE:
|
|
2004
|
+
• Free-form labels: skills, hashtags, email recipients, keywords
|
|
2005
|
+
• Predefined options → Combobox with multi-select (NOT TagInput)
|
|
2006
|
+
• Single value → TextInput
|
|
2007
|
+
• Limited options → Select / RadioGroup / Checkbox
|
|
2008
|
+
|
|
2009
|
+
allowDuplicates=false (default) prevents repeats. Use max to cap count.
|
|
2010
|
+
|
|
2011
|
+
ANTI-PATTERNS:
|
|
2012
|
+
✗ TagInput for predefined taxonomy → Combobox (controlled options)
|
|
2013
|
+
✗ TagInput without max for spam-prone fields
|
|
2014
|
+
✗ Custom comma-split string field → TagInput (proper UX + a11y)
|
|
1184
2015
|
|
|
1185
2016
|
| Prop | Type | Default | Description |
|
|
1186
2017
|
|---|---|---|---|
|
|
@@ -1200,7 +2031,20 @@ Tag input. Enter key to add, Backspace to delete last tag.
|
|
|
1200
2031
|
|
|
1201
2032
|
## NxImage
|
|
1202
2033
|
|
|
1203
|
-
|
|
2034
|
+
NxImage — lazy-loaded <img> with fallback, fixed aspect-ratio, object-fit. Always prefer over native <img>.
|
|
2035
|
+
|
|
2036
|
+
WHEN TO USE:
|
|
2037
|
+
• Content images: covers, banners, illustrations, hero images
|
|
2038
|
+
• Profile/user avatar (with text fallback) → Avatar
|
|
2039
|
+
• Background image / decorative → CSS bg-image
|
|
2040
|
+
• Need Next.js optimization → use next/image directly (NxImage doesn't optimize)
|
|
2041
|
+
|
|
2042
|
+
aspectRatio reserves space, preventing CLS (layout shift).
|
|
2043
|
+
|
|
2044
|
+
ANTI-PATTERNS:
|
|
2045
|
+
✗ <img> with manual onError handling → NxImage (built-in fallback)
|
|
2046
|
+
✗ NxImage without alt for content images (a11y violation)
|
|
2047
|
+
✗ NxImage without aspectRatio in fluid layouts → CLS jank
|
|
1204
2048
|
|
|
1205
2049
|
| Prop | Type | Default | Description |
|
|
1206
2050
|
|---|---|---|---|
|
|
@@ -1218,7 +2062,20 @@ Enhanced image. Lazy loading, fallback, aspect-ratio support.
|
|
|
1218
2062
|
|
|
1219
2063
|
## DatePicker
|
|
1220
2064
|
|
|
1221
|
-
DatePicker
|
|
2065
|
+
DatePicker — calendar popup for date selection. Based on react-day-picker.
|
|
2066
|
+
|
|
2067
|
+
WHEN TO USE:
|
|
2068
|
+
• Single date selection: birthday, due date, appointment
|
|
2069
|
+
• Date range → use two DatePickers with minDate/maxDate cross-bound
|
|
2070
|
+
• Time selection → not yet supported; use TextInput with type="time" + DatePicker combo
|
|
2071
|
+
• Quick relative ranges (Today/Last 7 days) → DropdownMenu of preset Buttons + DatePicker for "Custom"
|
|
2072
|
+
|
|
2073
|
+
minDate/maxDate disable out-of-range dates. locale="ko" for Korean labels.
|
|
2074
|
+
|
|
2075
|
+
ANTI-PATTERNS:
|
|
2076
|
+
✗ Native <input type="date"> for branded UI (inconsistent across browsers) → DatePicker
|
|
2077
|
+
✗ Free-text date with parsing → DatePicker (consistent UX)
|
|
2078
|
+
✗ DatePicker without minDate for past-date-invalid fields (e.g. booking)
|
|
1222
2079
|
|
|
1223
2080
|
| Prop | Type | Default | Description |
|
|
1224
2081
|
|---|---|---|---|
|
|
@@ -1237,7 +2094,19 @@ DatePicker. Calendar popup for date selection. Based on react-day-picker.
|
|
|
1237
2094
|
|
|
1238
2095
|
## ImageUpload
|
|
1239
2096
|
|
|
1240
|
-
ImageUpload
|
|
2097
|
+
ImageUpload — drag-and-drop image upload with preview, file-type/size validation, label/description.
|
|
2098
|
+
|
|
2099
|
+
WHEN TO USE:
|
|
2100
|
+
• Single image upload: avatar, cover, KYC document, post thumbnail
|
|
2101
|
+
• Multiple files / non-image → not yet supported; build custom or use a file dropzone
|
|
2102
|
+
• Inline image picker without preview → custom <input type="file">
|
|
2103
|
+
|
|
2104
|
+
accept whitelist + maxSize together cover validation. onError fires with i18n-ready string.
|
|
2105
|
+
|
|
2106
|
+
ANTI-PATTERNS:
|
|
2107
|
+
✗ <input type="file"> + manual preview → ImageUpload (handles drag, validation, preview)
|
|
2108
|
+
✗ ImageUpload without onError handler → silent failure on validation reject
|
|
2109
|
+
✗ Storing image as data-URL value (use File via onChange and upload to server)
|
|
1241
2110
|
|
|
1242
2111
|
| Prop | Type | Default | Description |
|
|
1243
2112
|
|---|---|---|---|
|