@tailor-platform/app-shell 1.15.0 → 1.16.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 (98) hide show
  1. package/dist/app-shell-core.css +1 -1
  2. package/dist/app-shell.d.ts +208 -114
  3. package/dist/app-shell.js +3449 -3446
  4. package/dist/{appshell-DLVBFo5J.js → appshell-B0wh5Lq0.js} +625 -512
  5. package/dist/style-entry.d.ts +116 -96
  6. package/dist/testing.d.ts +116 -96
  7. package/dist/testing.js +1 -1
  8. package/dist/vite-plugin.d.ts +116 -96
  9. package/package.json +8 -8
  10. package/skills/app-shell-patterns/SKILL.md +101 -14
  11. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  12. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  13. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  14. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  15. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  16. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  17. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  18. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  19. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  20. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  21. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  22. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  23. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  24. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
  25. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
  26. package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
  27. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  28. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  29. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  30. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  31. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  32. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  33. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  34. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  35. package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
  36. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  37. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  38. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  39. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  40. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  41. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  42. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  43. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  44. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  45. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  46. package/skills/app-shell-patterns/references/components/button.md +164 -0
  47. package/skills/app-shell-patterns/references/components/card.md +109 -0
  48. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  49. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  50. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  51. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  52. package/skills/app-shell-patterns/references/components/data-table.md +953 -0
  53. package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
  54. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  55. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  56. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  57. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  58. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  59. package/skills/app-shell-patterns/references/components/form.md +378 -0
  60. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  61. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  62. package/skills/app-shell-patterns/references/components/input.md +83 -0
  63. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  64. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  65. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  66. package/skills/app-shell-patterns/references/components/select.md +283 -0
  67. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  68. package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
  69. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  70. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  71. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  72. package/skills/app-shell-patterns/references/components/table.md +188 -0
  73. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  74. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  75. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  76. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  77. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  78. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  79. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  80. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  81. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
  82. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
  83. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  84. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
  85. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
  86. package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +258 -110
  87. package/skills/app-shell-patterns/references/migrations.md +34 -0
  88. package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +4 -27
  89. package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
  90. package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
  91. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  92. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  93. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  94. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  95. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +13 -27
  96. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  97. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
  98. package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
@@ -0,0 +1,169 @@
1
+ ---
2
+ title: Checkbox
3
+ description: Styled checkbox for boolean form fields, with indeterminate state and automatic Field / React Hook Form integration
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/checkbox.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Checkbox
9
+
10
+ The `Checkbox` component is a styled control for boolean fields. It wraps Base UI's checkbox, so it works with [`Field`](./form.md) and React Hook Form the same way `Input`, `Select`, and `Combobox` do — dropped inside a `Field.Root` it inherits label association, `aria-describedby`, `disabled`, and the invalid (error) state automatically.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Checkbox } from "@tailor-platform/app-shell";
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ <example-preview name="basic-usage"></example-preview>
21
+
22
+ ```tsx
23
+ function BasicUsage() {
24
+ return <Checkbox label="Subscribe to updates" />;
25
+ }
26
+ ```
27
+
28
+ Omit `label` to render just the box — useful for a table row-select cell (pass `aria-label`) or when a `Field.Label` sibling supplies the text.
29
+
30
+ ```tsx
31
+ <Checkbox aria-label="Select row" />
32
+ ```
33
+
34
+ ## Props
35
+
36
+ Accepts all Base UI `Checkbox.Root` props, plus `label`. The most common:
37
+
38
+ | Prop | Type | Default | Description |
39
+ | ----------------- | ------------------------------------- | ------- | ------------------------------------------------------------------------------------- |
40
+ | `label` | `React.ReactNode` | - | Inline label rendered next to the box (wraps both in a `<label>`). Omit for box-only. |
41
+ | `checked` | `boolean` | - | Controlled checked state. |
42
+ | `defaultChecked` | `boolean` | `false` | Initial checked state when uncontrolled. |
43
+ | `onCheckedChange` | `(checked: boolean, details) => void` | - | Called with the next boolean state. |
44
+ | `indeterminate` | `boolean` | `false` | Renders the "partially selected" (mixed) state. |
45
+ | `disabled` | `boolean` | `false` | Disables the control. |
46
+ | `required` | `boolean` | `false` | Marks the control as required for native/Field validation. |
47
+ | `name` | `string` | - | Field name for native form submission. |
48
+ | `value` | `string` | - | Submitted value when checked (native forms). |
49
+ | `inputRef` | `React.Ref<HTMLInputElement>` | - | Ref to the hidden native input (use for React Hook Form's `field.ref`). |
50
+ | `className` | `string` | - | Applied to the outer element — the `<label>` when `label` is set, else the box. |
51
+
52
+ ## Examples
53
+
54
+ ### Controlled
55
+
56
+ ```tsx
57
+ const [checked, setChecked] = useState(false);
58
+
59
+ <Checkbox label="Enable notifications" checked={checked} onCheckedChange={setChecked} />;
60
+ ```
61
+
62
+ ### Indeterminate ("select all")
63
+
64
+ ```tsx
65
+ <Checkbox
66
+ aria-label="Select all rows"
67
+ checked={allSelected}
68
+ indeterminate={someSelected && !allSelected}
69
+ onCheckedChange={(checked) => (checked ? selectAll() : clearAll())}
70
+ />
71
+ ```
72
+
73
+ ### Disabled
74
+
75
+ ```tsx
76
+ <Checkbox label="Unavailable option" disabled />
77
+ ```
78
+
79
+ ## Use With Field
80
+
81
+ Placed inside a `Field.Root`, `Checkbox` picks up the field's invalid and disabled state automatically — no `error` prop of its own. Use the inline `label` for the checkbox text and `Field.Error` for the message below it.
82
+
83
+ ```tsx
84
+ <Field.Root name="acceptTerms" error={{ message: "You must accept the terms." }}>
85
+ <Checkbox label="I accept the terms and conditions" />
86
+ <Field.Error>You must accept the terms.</Field.Error>
87
+ </Field.Root>
88
+ ```
89
+
90
+ ## React Hook Form
91
+
92
+ Drive the checkbox with a `Controller`. `onCheckedChange` hands React Hook Form a boolean directly, and spreading `fieldState` onto `Field.Root` wires the error/invalid state.
93
+
94
+ ```tsx
95
+ import { useForm, Controller } from "react-hook-form";
96
+
97
+ function TermsForm() {
98
+ const { control, handleSubmit } = useForm({ defaultValues: { acceptTerms: false } });
99
+
100
+ return (
101
+ <Form onSubmit={handleSubmit((data) => save(data))}>
102
+ <Controller
103
+ name="acceptTerms"
104
+ control={control}
105
+ rules={{ required: "You must accept the terms." }}
106
+ render={({ field, fieldState }) => (
107
+ <Field.Root name={field.name} {...fieldState}>
108
+ <Checkbox
109
+ label="I accept the terms and conditions"
110
+ checked={field.value}
111
+ onCheckedChange={field.onChange}
112
+ onBlur={field.onBlur}
113
+ inputRef={field.ref}
114
+ />
115
+ <Field.Error>{fieldState.error?.message}</Field.Error>
116
+ </Field.Root>
117
+ )}
118
+ />
119
+ <button type="submit">Save</button>
120
+ </Form>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ## With a shadcn-style `FormField`
126
+
127
+ `Checkbox` also drops into the common shadcn `FormField` / `FormControl` / `FormMessage`
128
+ pattern — no AppShell `Field` needed. `FormControl` injects `id`, `aria-describedby`, and
129
+ `aria-invalid` onto the control; `Checkbox` forwards them (and `ref`, in React 19) to the
130
+ underlying element and styles its error state off `aria-invalid`, so the invalid highlight
131
+ works here too.
132
+
133
+ ```tsx
134
+ <FormField
135
+ control={control}
136
+ name="acceptTerms"
137
+ render={({ field }) => (
138
+ <FormItem>
139
+ <FormControl>
140
+ <Checkbox
141
+ label="I accept the terms and conditions"
142
+ checked={field.value}
143
+ onCheckedChange={field.onChange}
144
+ onBlur={field.onBlur}
145
+ inputRef={field.ref}
146
+ />
147
+ </FormControl>
148
+ <FormMessage />
149
+ </FormItem>
150
+ )}
151
+ />
152
+ ```
153
+
154
+ > Use the inline `label` prop for the checkbox text (it wraps the box in a `<label>`, so
155
+ > clicking the text toggles it). A separate `FormLabel` with `htmlFor` is descriptive only —
156
+ > the control renders a `<span role="checkbox">`, which isn't a natively labelable element,
157
+ > so a sibling label won't toggle it on click.
158
+
159
+ ## TypeScript
160
+
161
+ ```typescript
162
+ import { type CheckboxProps } from "@tailor-platform/app-shell";
163
+ ```
164
+
165
+ ## Related Components
166
+
167
+ - [Form, Field, and Fieldset](./form.md) — Checkbox integrates with `Field` for labels, errors, and validation.
168
+ - [Input](./input.md) — Text input counterpart.
169
+ - [Textarea](./textarea.md) — Multi-line text counterpart.
@@ -0,0 +1,318 @@
1
+ ---
2
+ title: Combobox
3
+ description: Searchable combobox with single/multi selection, built-in filtering, async data fetching, and user-creatable items
4
+ ---
5
+
6
+ <!-- GENERATED by docs-kit from docs-src/components/combobox.docs.outline.md — do not edit by hand. -->
7
+
8
+ # Combobox
9
+
10
+ The `Combobox` component provides a searchable combobox with built-in filtering. Pass `items` and get a ready-to-use combobox out of the box. For async data fetching use `Combobox.Async`. For user-created items add an `onCreateItem` prop. For custom compositions use `Combobox.Parts`.
11
+
12
+ ## Import
13
+
14
+ ```tsx
15
+ import { Combobox } from "@tailor-platform/app-shell";
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ <example-preview name="basic-usage"></example-preview>
21
+
22
+ ```tsx
23
+ function BasicUsage() {
24
+ return (
25
+ <Combobox
26
+ items={["Apple", "Banana", "Cherry"]}
27
+ placeholder="Search fruits..."
28
+ onValueChange={(value) => console.log(value)}
29
+ />
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Props
35
+
36
+ ### Combobox Props
37
+
38
+ | Prop | Type | Default | Description |
39
+ | ------------------- | -------------------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
40
+ | `items` | `I[]` | - | Items to display. May be a flat array or an array of `ItemGroup<T>` |
41
+ | `placeholder` | `string` | - | Placeholder text for the input |
42
+ | `emptyText` | `string` | `"No results."` | Text shown when no items match |
43
+ | `multiple` | `true \| false \| undefined` | `false` | Enables multi-select mode |
44
+ | `value` | `T \| null` (single) or `T[]` (multiple) | - | Controlled value |
45
+ | `defaultValue` | `T \| null` (single) or `T[]` (multiple) | - | Initial value (uncontrolled) |
46
+ | `onValueChange` | `(value: T \| null) => void` (single) or `(value: T[]) => void` (multiple) | - | Called when the selected value changes |
47
+ | `mapItem` | `(item: T) => MappedItem` | - | Map each item to its label, key, and optional custom render |
48
+ | `className` | `string` | - | Additional CSS classes for the root container |
49
+ | `disabled` | `boolean` | `false` | Disables the combobox |
50
+ | `aria-label` | `string` | - | Accessible name for the input. Use when there is no visible label |
51
+ | `aria-labelledby` | `string` | - | ID of the element(s) that label the input |
52
+ | `id` | `string` | - | ID applied to the combobox input element |
53
+ | `onCreateItem` | `(value: string) => T \| false \| Promise<T \| false>` | - | Enable user-created items (requires `mapItem`; `T` must be an object type) |
54
+ | `formatCreateLabel` | `(value: string) => string` | `` (v) => `Create "${v}"` `` | Format the label for the "create" option |
55
+
56
+ ### MappedItem
57
+
58
+ ```ts
59
+ interface MappedItem {
60
+ label: string; // Display text, used for filtering and a11y
61
+ key?: string; // React key. Defaults to label
62
+ render?: React.ReactNode; // Custom JSX to render in the dropdown
63
+ }
64
+ ```
65
+
66
+ ### ItemGroup
67
+
68
+ ```ts
69
+ interface ItemGroup<T> {
70
+ label: string;
71
+ items: T[];
72
+ }
73
+ ```
74
+
75
+ ## Grouped Items
76
+
77
+ ```tsx
78
+ const fruits = [
79
+ { label: "Citrus", items: ["Orange", "Lemon", "Lime"] },
80
+ { label: "Berries", items: ["Strawberry", "Blueberry"] },
81
+ ];
82
+
83
+ <Combobox items={fruits} placeholder="Search fruits..." />;
84
+ ```
85
+
86
+ ## Multi-select
87
+
88
+ In multi-select mode, selected items are displayed as chips inside the input:
89
+
90
+ ```tsx
91
+ <Combobox
92
+ items={["Red", "Green", "Blue"]}
93
+ multiple
94
+ placeholder="Pick colors"
95
+ onValueChange={(colors) => console.log(colors)}
96
+ />
97
+ ```
98
+
99
+ ## Creatable Items
100
+
101
+ Add `onCreateItem` to let users create new items on-the-fly. `T` must be an object type:
102
+
103
+ ```tsx
104
+ type Tag = { id: string; name: string };
105
+
106
+ const [tags, setTags] = useState<Tag[]>([
107
+ { id: "1", name: "Bug" },
108
+ { id: "2", name: "Feature" },
109
+ ]);
110
+
111
+ <Combobox
112
+ items={tags}
113
+ mapItem={(tag) => ({ label: tag.name, key: tag.id })}
114
+ onCreateItem={(value) => {
115
+ const newTag: Tag = { id: crypto.randomUUID(), name: value };
116
+ setTags((prev) => [...prev, newTag]);
117
+ return newTag; // return the new item to add it to the selection
118
+ }}
119
+ placeholder="Search or create a tag..."
120
+ />;
121
+ ```
122
+
123
+ `onCreateItem` may return:
124
+
125
+ - `T` — accept the item and add it to the selection
126
+ - `false` — cancel the creation
127
+ - `Promise<T | false>` — for async workflows
128
+
129
+ ## Async Loading
130
+
131
+ Use `Combobox.Async` to load items from an API. The fetcher is called on each keystroke (debounced). When the dropdown first opens or the input is cleared, the fetcher receives `null` as the query — return initial/default items for `null`, or return an empty array to show nothing until the user starts typing.
132
+
133
+ ```tsx
134
+ import { type ComboboxAsyncFetcher } from "@tailor-platform/app-shell";
135
+
136
+ const fetcher: ComboboxAsyncFetcher<User> = async (query, { signal }) => {
137
+ const res = await fetch(`/api/users?q=${query ?? ""}`, { signal });
138
+ return res.json();
139
+ };
140
+
141
+ <Combobox.Async
142
+ fetcher={fetcher}
143
+ mapItem={(user) => ({ label: user.name, key: user.id })}
144
+ placeholder="Search users..."
145
+ onValueChange={(user) => console.log(user)}
146
+ />;
147
+ ```
148
+
149
+ `Combobox.Async` also supports `onCreateItem` for creatable async comboboxes.
150
+
151
+ ### Combobox.Async Props
152
+
153
+ Accepts all the same props as `Combobox` except `items`, plus:
154
+
155
+ | Prop | Type | Default | Description |
156
+ | -------------- | -------------------------- | -------------------------- | ------------------------------------------------------------------ |
157
+ | `fetcher` | `ComboboxAsyncFetcher<T>` | - | Fetcher called on each keystroke (debounced by default) |
158
+ | `loadingText` | `string` | `"Loading..."` | Text shown while loading |
159
+ | `errorText` | `string` | `"Couldn't load results."` | Message shown in the popover when the fetcher fails (with Retry) |
160
+ | `retryText` | `string` | `"Retry"` | Label for the retry button in the error state |
161
+ | `onFetchError` | `(error: unknown) => void` | - | Called once per outage when a fetch fails (logging/error tracking) |
162
+
163
+ ### ComboboxAsyncFetcher
164
+
165
+ ```ts
166
+ type ComboboxAsyncFetcher<T> =
167
+ | ((query: string | null, options: { signal: AbortSignal }) => Promise<T[]>)
168
+ | {
169
+ fn: (query: string | null, options: { signal: AbortSignal }) => Promise<T[]>;
170
+ debounceMs: number;
171
+ };
172
+ ```
173
+
174
+ `query` is `null` when the user has not typed anything (e.g. the dropdown was just opened or the input was cleared). Pass `{ fn, debounceMs }` to customize the debounce delay.
175
+
176
+ ### Error handling
177
+
178
+ If the fetcher throws or rejects, `Combobox.Async` renders a built-in inline error state in the popover — the `errorText` message plus a **Retry** button that re-runs the last fetch — instead of the misleading "No results." empty state. Aborted/superseded requests (a keystroke replaced by a newer one) are ignored, and the component de-dupes per outage so typing during an outage doesn't stack repeated announcements.
179
+
180
+ To run a side effect on failure (log to error tracking, show a toast), pass `onFetchError` — it fires **once per outage** (on the transition into the error state) and re-arms after the next successful fetch. The inline error state is shown regardless.
181
+
182
+ ```tsx
183
+ <Combobox.Async
184
+ fetcher={fetcher}
185
+ errorText="Couldn't load users."
186
+ retryText="Try again"
187
+ onFetchError={(error) => reportError(error)}
188
+ />
189
+ ```
190
+
191
+ ## Low-level Primitives
192
+
193
+ `Combobox.Parts` exposes styled sub-components and hooks for fully custom compositions:
194
+
195
+ ```tsx
196
+ const {
197
+ Root,
198
+ InputGroup,
199
+ Input,
200
+ Trigger,
201
+ Content,
202
+ List,
203
+ Item,
204
+ Empty,
205
+ Group,
206
+ GroupLabel,
207
+ Clear,
208
+ Chips,
209
+ Chip,
210
+ ChipRemove,
211
+ Value,
212
+ Collection,
213
+ Status,
214
+ } = Combobox.Parts;
215
+ ```
216
+
217
+ ## Form submission
218
+
219
+ Inside a `Field.Root`, the field's `name` identifies the value and these props are unnecessary —
220
+ see [Form](./form.md). They matter for **native** submission: a plain `<form>`, `new FormData(form)`,
221
+ or a server action.
222
+
223
+ | Prop | Type | Default | Description |
224
+ | ------------------- | ----------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
225
+ | `name` | `string` | - | Names the hidden input for native submission. **Ignored inside a `Field.Root`** — the field's name wins. |
226
+ | `form` | `string` | - | `id` of the owning form, when the control is rendered outside it. |
227
+ | `required` | `boolean` | `false` | Blocks submission until a value is chosen, surfacing the matching `Field.Error`. |
228
+ | `inputRef` | `React.Ref<HTMLInputElement>` | - | Ref to the hidden input (use for React Hook Form's `field.ref`). |
229
+ | `itemToStringValue` | `(item: T) => string` | - | Serialises a non-string item for submission. Items shaped `{ value, label }` use `value` automatically. Not available on the creatable variant, which derives its own. |
230
+
231
+ `itemToStringValue` is not accepted on the creatable variant (`onCreateItem`): `Combobox` derives
232
+ its own there so the pending-item sentinel serialises correctly. A creatable combobox over object
233
+ items therefore cannot customise the submitted value — use the non-creatable variant, or map the
234
+ value in your submit handler.
235
+
236
+ ## Examples
237
+
238
+ ### Controlled Combobox
239
+
240
+ ```tsx
241
+ const [selected, setSelected] = useState<User | null>(null);
242
+
243
+ <Combobox
244
+ items={users}
245
+ mapItem={(u) => ({ label: u.name, key: u.id })}
246
+ value={selected}
247
+ onValueChange={setSelected}
248
+ placeholder="Select a user"
249
+ />;
250
+ ```
251
+
252
+ ### Async with Parts (custom composition)
253
+
254
+ Combine `Combobox.useAsync` with `Combobox.Parts` for full control over layout and rendering:
255
+
256
+ `Combobox.useAsync` accepts the same options as `Combobox.Async` (including `onFetchError`) and returns:
257
+
258
+ | Property | Type | Description |
259
+ | --------- | ------------ | ------------------------------------------------------- |
260
+ | `items` | `T[]` | Currently loaded items |
261
+ | `loading` | `boolean` | Whether a fetch is in progress |
262
+ | `error` | `unknown` | The error thrown by the last fetch, if any |
263
+ | `retry` | `() => void` | Re-runs the last fetch (use to build a custom retry UI) |
264
+
265
+ ```tsx
266
+ type Country = { code: string; name: string };
267
+
268
+ const countries = Combobox.useAsync({
269
+ fetcher: async (query, { signal }) => {
270
+ const res = await fetch(`/api/countries?q=${query ?? ""}`, { signal });
271
+ if (!res.ok) return [];
272
+ return res.json() as Promise<Country[]>;
273
+ },
274
+ onFetchError: (error) => reportError(error),
275
+ });
276
+
277
+ <Combobox.Parts.Root {...countries} filter={null} itemToStringLabel={(c) => c.name}>
278
+ <Combobox.Parts.InputGroup>
279
+ <Combobox.Parts.Input placeholder="Search countries..." />
280
+ <Combobox.Parts.Clear />
281
+ <Combobox.Parts.Trigger />
282
+ </Combobox.Parts.InputGroup>
283
+ <Combobox.Parts.Content>
284
+ <Combobox.Parts.List>
285
+ {countries.items.map((c) => (
286
+ <Combobox.Parts.Item key={c.code} value={c}>
287
+ {c.name}
288
+ </Combobox.Parts.Item>
289
+ ))}
290
+ <Combobox.Parts.Empty>
291
+ {countries.error ? (
292
+ <button onClick={countries.retry}>Couldn't load. Retry</button>
293
+ ) : countries.loading ? (
294
+ "Loading..."
295
+ ) : (
296
+ "No results."
297
+ )}
298
+ </Combobox.Parts.Empty>
299
+ </Combobox.Parts.List>
300
+ </Combobox.Parts.Content>
301
+ </Combobox.Parts.Root>;
302
+ ```
303
+
304
+ ## Accessibility
305
+
306
+ - Input is keyboard accessible with arrow key navigation
307
+ - Pressing `Escape` closes the dropdown
308
+ - Multi-select chips have `aria-label` set from the item label
309
+ - When used standalone (no visible `<label>`), give the input an accessible name with `aria-label` or `aria-labelledby` — otherwise screen readers announce only the current value:
310
+
311
+ ```tsx
312
+ <Combobox items={items} aria-label="Fruit filter" />
313
+ ```
314
+
315
+ ## Related Components
316
+
317
+ - [Select](./select.md) - Non-searchable dropdown
318
+ - [Autocomplete](./autocomplete.md) - Free-text input with suggestions