@ceed/cds 1.24.1-next.3 → 1.26.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 (65) hide show
  1. package/dist/chunks/rehype-accent-FZRUD7VI.js +39 -0
  2. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  3. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  4. package/dist/components/DataTable/components.d.ts +2 -1
  5. package/dist/components/DataTable/hooks.d.ts +1 -1
  6. package/dist/components/DataTable/styled.d.ts +3 -1
  7. package/dist/components/DataTable/types.d.ts +11 -0
  8. package/dist/components/DataTable/utils.d.ts +2 -2
  9. package/dist/components/RadioTileGroup/RadioTileGroup.d.ts +56 -0
  10. package/dist/components/RadioTileGroup/index.d.ts +3 -0
  11. package/dist/components/data-display/DataTable.md +177 -1
  12. package/dist/components/data-display/InfoSign.md +74 -91
  13. package/dist/components/data-display/Typography.md +411 -94
  14. package/dist/components/feedback/CircularProgress.md +257 -0
  15. package/dist/components/feedback/Dialog.md +76 -62
  16. package/dist/components/feedback/Modal.md +430 -138
  17. package/dist/components/feedback/Skeleton.md +280 -0
  18. package/dist/components/feedback/llms.txt +2 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/inputs/Autocomplete.md +356 -107
  21. package/dist/components/inputs/ButtonGroup.md +115 -104
  22. package/dist/components/inputs/CurrencyInput.md +183 -5
  23. package/dist/components/inputs/DatePicker.md +108 -431
  24. package/dist/components/inputs/DateRangePicker.md +131 -492
  25. package/dist/components/inputs/FilterableCheckboxGroup.md +145 -19
  26. package/dist/components/inputs/FormControl.md +361 -0
  27. package/dist/components/inputs/IconButton.md +137 -88
  28. package/dist/components/inputs/Input.md +204 -73
  29. package/dist/components/inputs/MonthPicker.md +95 -422
  30. package/dist/components/inputs/MonthRangePicker.md +89 -466
  31. package/dist/components/inputs/PercentageInput.md +185 -16
  32. package/dist/components/inputs/RadioButton.md +163 -35
  33. package/dist/components/inputs/RadioList.md +241 -0
  34. package/dist/components/inputs/RadioTileGroup.md +507 -0
  35. package/dist/components/inputs/Select.md +222 -326
  36. package/dist/components/inputs/Slider.md +334 -0
  37. package/dist/components/inputs/Switch.md +143 -376
  38. package/dist/components/inputs/Textarea.md +213 -10
  39. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  40. package/dist/components/inputs/llms.txt +4 -0
  41. package/dist/components/navigation/Breadcrumbs.md +57 -308
  42. package/dist/components/navigation/Drawer.md +180 -0
  43. package/dist/components/navigation/Dropdown.md +98 -215
  44. package/dist/components/navigation/IconMenuButton.md +40 -502
  45. package/dist/components/navigation/InsetDrawer.md +281 -650
  46. package/dist/components/navigation/Link.md +31 -348
  47. package/dist/components/navigation/Menu.md +92 -285
  48. package/dist/components/navigation/MenuButton.md +55 -448
  49. package/dist/components/navigation/Pagination.md +47 -338
  50. package/dist/components/navigation/Stepper.md +160 -28
  51. package/dist/components/navigation/Tabs.md +57 -316
  52. package/dist/components/surfaces/Accordions.md +49 -804
  53. package/dist/components/surfaces/Card.md +97 -157
  54. package/dist/components/surfaces/Divider.md +83 -234
  55. package/dist/components/surfaces/Sheet.md +153 -328
  56. package/dist/guides/ThemeProvider.md +89 -0
  57. package/dist/guides/llms.txt +9 -0
  58. package/dist/index.browser.js +224 -0
  59. package/dist/index.browser.js.map +7 -0
  60. package/dist/index.cjs +726 -425
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.js +641 -396
  63. package/dist/llms.txt +9 -0
  64. package/framer/index.js +1 -163
  65. package/package.json +22 -17
@@ -8,10 +8,36 @@ Autocomplete is an enhanced input component that provides real-time suggestions
8
8
  <Autocomplete options={['Option1', 'Option2']} />
9
9
  ```
10
10
 
11
- | Field | Description | Default |
12
- | ------- | ----------- | ------- |
13
- | label | — | — |
14
- | loading | — | — |
11
+ | Field | Description | Default |
12
+ | --------------------- | ----------- | ------- |
13
+ | variant | — | — |
14
+ | color | — | — |
15
+ | size | — | — |
16
+ | placeholder | — | — |
17
+ | label | — | — |
18
+ | helperText | — | — |
19
+ | error | — | — |
20
+ | required | — | — |
21
+ | disabled | — | — |
22
+ | readOnly | — | — |
23
+ | multiple | — | — |
24
+ | freeSolo | — | — |
25
+ | disableClearable | — | — |
26
+ | autoHighlight | — | — |
27
+ | clearOnEscape | — | — |
28
+ | disableCloseOnSelect | — | — |
29
+ | openOnFocus | — | — |
30
+ | blurOnSelect | — | — |
31
+ | filterSelectedOptions | — | — |
32
+ | selectOnFocus | — | — |
33
+ | clearOnBlur | — | — |
34
+ | forcePopupIcon | — | — |
35
+ | loading | — | — |
36
+ | noOptionsText | — | — |
37
+ | loadingText | — | — |
38
+ | limitTags | — | — |
39
+ | value | — | — |
40
+ | defaultValue | — | — |
15
41
 
16
42
  > ⚠️ **Usage Warning** ⚠️
17
43
  >
@@ -22,6 +48,11 @@ Autocomplete is an enhanced input component that provides real-time suggestions
22
48
  > - **Dropdown**: For action menus, not form value selection
23
49
  > - **Input**: For free-text entry without predefined options
24
50
 
51
+ > 💡 **Use built-in form props**
52
+ >
53
+ > This component natively supports form elements such as `label` and `helperText` props.
54
+ > When building forms, use these built-in props instead of manually composing labels and helper text with Typography.
55
+
25
56
  ## Usage
26
57
 
27
58
  ```tsx
@@ -42,17 +73,19 @@ function CountrySelector() {
42
73
  }
43
74
  ```
44
75
 
45
- ## Examples
46
-
47
- ### Playground
76
+ ## Variants
48
77
 
49
- Interactive example with basic string options.
78
+ The `variant` prop controls the visual style of the autocomplete input. Available variants: `outlined` (default), `soft`, `solid`, `plain`.
50
79
 
51
80
  ```tsx
52
- <Autocomplete options={['Option1', 'Option2']} />
81
+ <Stack gap={2} p={2}>
82
+ {variants.map(variant => <Autocomplete key={variant} variant={variant} options={sampleOptions} placeholder={variant} label={variant} sx={{
83
+ maxWidth: 300
84
+ }} />)}
85
+ </Stack>
53
86
  ```
54
87
 
55
- ### Sizes
88
+ ## Sizes
56
89
 
57
90
  Available size options: `sm`, `md`, `lg`.
58
91
 
@@ -67,7 +100,139 @@ Available size options: `sm`, `md`, `lg`.
67
100
  </div>
68
101
  ```
69
102
 
70
- ### Option Groups
103
+ ## Colors
104
+
105
+ The `color` prop changes the color scheme. Available colors: `primary`, `neutral`, `danger`, `success`, `warning`.
106
+
107
+ ```tsx
108
+ <Stack gap={2} p={2}>
109
+ {colors.map(color => <Autocomplete key={color} color={color} options={sampleOptions} defaultValue="Apple" label={color} sx={{
110
+ maxWidth: 300
111
+ }} />)}
112
+ </Stack>
113
+ ```
114
+
115
+ ## States
116
+
117
+ Autocomplete supports several form states for different interaction contexts.
118
+
119
+ ### Disabled
120
+
121
+ A disabled autocomplete cannot be focused or interacted with. Use for fields that are temporarily unavailable.
122
+
123
+ ### Read Only
124
+
125
+ A read-only autocomplete can be focused and its value copied, but cannot be changed. Use when a value should be visible but not editable.
126
+
127
+ ### Error
128
+
129
+ Set `error` along with `helperText` to indicate validation failures.
130
+
131
+ ### Required
132
+
133
+ Set `required` to mark the field as required in a form.
134
+
135
+ ```tsx
136
+ <Stack gap={3} p={2} sx={{
137
+ maxWidth: 300
138
+ }}>
139
+ <Autocomplete label="Disabled" options={sampleOptions} defaultValue="Apple" disabled />
140
+ <Autocomplete label="Read Only" options={sampleOptions} defaultValue="Banana" readOnly />
141
+ <Autocomplete label="Error" options={sampleOptions} error helperText="This field is required" />
142
+ <Autocomplete label="Required" options={sampleOptions} required placeholder="Select a fruit..." />
143
+ </Stack>
144
+ ```
145
+
146
+ ## Free Solo
147
+
148
+ The `freeSolo` prop allows users to type arbitrary values that are not in the options list. This is useful for inputs where the user can either pick a suggestion or enter a custom value.
149
+
150
+ > ⚠️ **Known limitation**: The component's internal `handleChange` accesses `newValue.value`, which is `undefined` for raw strings in freeSolo mode. Typed arbitrary values may not propagate correctly through `onChange`. Consider using `onInputChange` as an alternative for capturing free-text input.
151
+
152
+ ```tsx
153
+ <Stack gap={2} p={2} sx={{
154
+ maxWidth: 300
155
+ }}>
156
+ <Autocomplete label="Free Solo" options={sampleOptions} freeSolo placeholder="Type anything..." value={value} onChange={e => setValue(e.target.value)} />
157
+ <Typography level="body-sm" textColor="text.tertiary">
158
+ Current value: {value ?? '(empty)'}
159
+ </Typography>
160
+ <Typography level="body-xs" textColor="text.tertiary">
161
+ Note: freeSolo allows arbitrary text input. However, the component&apos;s onChange handler accesses
162
+ newValue.value which is undefined for raw strings, so typed values may not propagate correctly.
163
+ </Typography>
164
+ </Stack>
165
+ ```
166
+
167
+ ## Disable Clearable
168
+
169
+ Set `disableClearable` to hide the clear (X) button. This forces the user to always have a selection.
170
+
171
+ ```tsx
172
+ <Autocomplete
173
+ options={sampleOptions}
174
+ defaultValue="Cherry"
175
+ disableClearable
176
+ label="Disable Clearable"
177
+ sx={{
178
+ maxWidth: 300
179
+ }}
180
+ />
181
+ ```
182
+
183
+ ## Custom Texts
184
+
185
+ Customize the messages shown when there are no matching options or while loading.
186
+
187
+ ```tsx
188
+ <Stack gap={3} direction="row" p={2}>
189
+ <Autocomplete label="Custom No Options Text" options={[]} noOptionsText="No fruits found — try a different search" placeholder="Search fruits..." sx={{
190
+ minWidth: 280
191
+ }} />
192
+ <Autocomplete label="Custom Loading Text" options={[]} loading loadingText="Fetching fruits from the orchard..." placeholder="Loading fruits..." sx={{
193
+ minWidth: 280
194
+ }} />
195
+ </Stack>
196
+ ```
197
+
198
+ ## Multiple Selection
199
+
200
+ Enable `multiple` to allow selecting more than one option. Selected values are shown as chips.
201
+
202
+ ```tsx
203
+ <div style={{
204
+ display: 'flex',
205
+ flexDirection: 'column',
206
+ gap: '10px'
207
+ }}>
208
+ <Autocomplete {...args} size="sm" />
209
+ <Autocomplete {...args} size="md" />
210
+ <Autocomplete {...args} size="lg" />
211
+ </div>
212
+ ```
213
+
214
+ ### Limit Tags
215
+
216
+ In multiple mode, use `limitTags` to control how many tags are visible before truncating. Use `filterSelectedOptions` to hide already-selected options from the dropdown.
217
+
218
+ > 💡 The component uses a custom `renderTags` implementation. JoyUI's `limitTags` truncates the array passed to `renderTags`, so tags will be limited, but the default "+N more" indicator text may not appear.
219
+
220
+ ```tsx
221
+ <Stack gap={3} p={2} sx={{
222
+ maxWidth: 400
223
+ }}>
224
+ <Stack gap={1}>
225
+ <Typography level="title-sm">limitTags=2</Typography>
226
+ <Autocomplete multiple options={sampleOptions} defaultValue={['Apple', 'Banana', 'Cherry', 'Date']} limitTags={2} label="Limit Tags" />
227
+ </Stack>
228
+ <Stack gap={1}>
229
+ <Typography level="title-sm">filterSelectedOptions</Typography>
230
+ <Autocomplete multiple options={sampleOptions} defaultValue={['Apple', 'Banana']} filterSelectedOptions label="Filter Selected Options" />
231
+ </Stack>
232
+ </Stack>
233
+ ```
234
+
235
+ ## Option Groups
71
236
 
72
237
  Group options into categories using the `groupBy` function.
73
238
 
@@ -95,7 +260,7 @@ Group options into categories using the `groupBy` function.
95
260
  />
96
261
  ```
97
262
 
98
- ### Custom Options
263
+ ## Custom Options
99
264
 
100
265
  Options can include decorators for rich content display.
101
266
 
@@ -116,7 +281,28 @@ Options can include decorators for rich content display.
116
281
  />
117
282
  ```
118
283
 
119
- ### Loading State
284
+ ## Secondary Text
285
+
286
+ Options can display a secondary line of text using the `secondaryText` property. This is useful for showing additional context like emails, phone numbers, or descriptions alongside the main label.
287
+
288
+ ```tsx
289
+ <Stack gap={4} direction="row" alignItems="flex-start" p={2}>
290
+ {sizes.map(size => <Stack key={size} gap={1}>
291
+ <span style={{
292
+ color: '#6366f1',
293
+ fontSize: 12
294
+ }}>{size}</span>
295
+ <Autocomplete placeholder="Placeholder" options={optionsWithSecondaryText} value={values[size]} onChange={e => setValues(prev => ({
296
+ ...prev,
297
+ [size]: e.target.value
298
+ }))} sx={{
299
+ minWidth: 200
300
+ }} size={size} />
301
+ </Stack>)}
302
+ </Stack>
303
+ ```
304
+
305
+ ## Loading State
120
306
 
121
307
  Show a loading indicator while fetching options.
122
308
 
@@ -127,6 +313,8 @@ Show a loading indicator while fetching options.
127
313
  />
128
314
  ```
129
315
 
316
+ ## Controlled / Uncontrolled
317
+
130
318
  ### Controlled
131
319
 
132
320
  Manage value externally with controlled state.
@@ -153,6 +341,67 @@ Manage value externally with controlled state.
153
341
  </Stack>
154
342
  ```
155
343
 
344
+ ### Uncontrolled
345
+
346
+ Let the component manage its own state with `defaultValue`.
347
+
348
+ ```tsx
349
+ <Autocomplete
350
+ options={['Uncontrolled', 'Controlled']}
351
+ label="Component Type"
352
+ defaultValue="Controlled"
353
+ />
354
+ ```
355
+
356
+ ## Virtualization
357
+
358
+ Autocomplete automatically virtualizes long option lists for performance. This example renders 1,000 options efficiently.
359
+
360
+ ```tsx
361
+ <Autocomplete
362
+ options={(() => {
363
+ const res: any[] = [];
364
+ for (let i = 0; i < 1000; i++) {
365
+ res.push(i);
366
+ }
367
+ return res;
368
+ })()}
369
+ />
370
+ ```
371
+
372
+ ## Behavior Props
373
+
374
+ These boolean props fine-tune interaction behavior. Toggle them in the Controls panel below to see their effects.
375
+
376
+ | Prop | Default | Effect |
377
+ | ---------------------- | -------- | -------------------------------------------------------------- |
378
+ | `autoHighlight` | `false` | Automatically highlights the first option when the popup opens |
379
+ | `clearOnEscape` | `false` | Clears the input value when the Escape key is pressed |
380
+ | `disableCloseOnSelect` | `false` | Keeps the popup open after selecting an option |
381
+ | `openOnFocus` | `false` | Opens the popup when the input receives focus |
382
+ | `blurOnSelect` | `false` | Blurs the input after an option is selected |
383
+ | `selectOnFocus` | `false` | Selects all input text when the input is focused |
384
+ | `clearOnBlur` | `true` | Clears the input text on blur if it doesn't match any option |
385
+ | `forcePopupIcon` | `'auto'` | Controls whether the popup icon is always shown |
386
+
387
+ ```tsx
388
+ <Autocomplete
389
+ options={sampleOptions}
390
+ label="Behavior Props"
391
+ placeholder="Toggle props in the Controls panel..."
392
+ autoHighlight={false}
393
+ clearOnEscape={false}
394
+ disableCloseOnSelect={false}
395
+ openOnFocus={false}
396
+ blurOnSelect={false}
397
+ selectOnFocus={false}
398
+ clearOnBlur
399
+ sx={{
400
+ maxWidth: 400
401
+ }}
402
+ />
403
+ ```
404
+
156
405
  ## When to Use
157
406
 
158
407
  ### ✅ Good Use Cases
@@ -227,7 +476,7 @@ function UserSearch({ onSelect }) {
227
476
  label: user.name,
228
477
  secondaryText: user.email,
229
478
  startDecorator: <Avatar src={user.avatar} size="sm" />,
230
- }))
479
+ })),
231
480
  );
232
481
  } finally {
233
482
  setLoading(false);
@@ -304,7 +553,7 @@ function AddressAutocomplete({ onAddressSelect }) {
304
553
  value: result.placeId,
305
554
  label: result.formattedAddress,
306
555
  secondaryText: result.city + ', ' + result.country,
307
- }))
556
+ })),
308
557
  );
309
558
  } finally {
310
559
  setLoading(false);
@@ -396,13 +645,7 @@ function ContactSelector() {
396
645
  { value: 'michael', label: 'Michael Chen', secondaryText: '(646) 555-0876' },
397
646
  ];
398
647
 
399
- return (
400
- <Autocomplete
401
- label="Contact"
402
- placeholder="Search contacts..."
403
- options={contacts}
404
- />
405
- );
648
+ return <Autocomplete label="Contact" placeholder="Search contacts..." options={contacts} />;
406
649
  }
407
650
  ```
408
651
 
@@ -443,30 +686,46 @@ function LazyAutocomplete({ fetchOptions }) {
443
686
 
444
687
  ### Key Props
445
688
 
446
- | Prop | Type | Default | Description |
447
- | --------------- | ---------------------------------------- | ------- | ------------------------------------- |
448
- | `options` | `string[] \| OptionObject[]` | `[]` | Array of options (strings or objects) |
449
- | `value` | `string \| string[]` | - | Selected value(s) for controlled mode |
450
- | `defaultValue` | `string \| string[]` | - | Initial value for uncontrolled mode |
451
- | `onChange` | `(event: { target: { value } }) => void` | - | Callback when selection changes |
452
- | `onInputChange` | `(event: { target: { value } }) => void` | - | Callback when input text changes |
453
- | `label` | `string` | - | Label text above the input |
454
- | `placeholder` | `string` | - | Placeholder text when empty |
455
- | `loading` | `boolean` | `false` | Show loading indicator |
456
- | `multiple` | `boolean` | `false` | Allow multiple selections |
457
- | `groupBy` | `(option) => string` | - | Function to group options |
458
- | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
459
- | `disabled` | `boolean` | `false` | Disable the input |
689
+ | Prop | Type | Default | Description |
690
+ | ----------------------- | -------------------------------------------------------------- | -------------- | ----------------------------------------------------- |
691
+ | `options` | `string[] \| OptionObject[]` | `[]` | Array of options (strings or objects) |
692
+ | `value` | `string \| string[]` | - | Selected value(s) for controlled mode |
693
+ | `defaultValue` | `string \| string[]` | - | Initial value for uncontrolled mode |
694
+ | `onChange` | `(event: { target: { value } }) => void` | - | Callback when selection changes |
695
+ | `onInputChange` | `(event: { target: { value } }) => void` | - | Callback when input text changes |
696
+ | `label` | `ReactNode` | - | Label text above the input |
697
+ | `placeholder` | `string` | - | Placeholder text when empty |
698
+ | `loading` | `boolean` | `false` | Show loading indicator |
699
+ | `multiple` | `boolean` | `false` | Allow multiple selections |
700
+ | `groupBy` | `(option) => string` | - | Function to group options |
701
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input size |
702
+ | `variant` | `'outlined' \| 'soft' \| 'solid' \| 'plain'` | `'outlined'` | Visual style variant |
703
+ | `color` | `'primary' \| 'neutral' \| 'danger' \| 'success' \| 'warning'` | `'neutral'` | Color scheme |
704
+ | `disabled` | `boolean` | `false` | Disable the input |
705
+ | `readOnly` | `boolean` | `false` | Make the input read-only (focusable but not editable) |
706
+ | `required` | `boolean` | `false` | Mark the field as required |
707
+ | `error` | `boolean` | `false` | Indicate an error state |
708
+ | `helperText` | `ReactNode` | - | Helper text below the input |
709
+ | `freeSolo` | `boolean` | `false` | Allow arbitrary values not in the options list |
710
+ | `disableClearable` | `boolean` | `false` | Hide the clear (X) button |
711
+ | `noOptionsText` | `ReactNode` | `'No options'` | Text when no options match |
712
+ | `loadingText` | `ReactNode` | `'Loading…'` | Text while loading |
713
+ | `limitTags` | `number` | `-1` | Max visible tags in multiple mode (-1 for unlimited) |
714
+ | `autoHighlight` | `boolean` | `false` | Auto-highlight first option |
715
+ | `clearOnEscape` | `boolean` | `false` | Clear value on Escape key |
716
+ | `disableCloseOnSelect` | `boolean` | `false` | Keep popup open after selection |
717
+ | `openOnFocus` | `boolean` | `false` | Open popup on focus |
718
+ | `filterSelectedOptions` | `boolean` | `false` | Hide selected options from dropdown (multiple mode) |
460
719
 
461
720
  ### Option Object Structure
462
721
 
463
722
  ```tsx
464
723
  interface OptionObject {
465
- value: string; // Unique value identifier
466
- label: string; // Display text
467
- secondaryText?: string; // Secondary line of text
724
+ value: string; // Unique value identifier
725
+ label: string; // Display text
726
+ secondaryText?: string; // Secondary line of text
468
727
  startDecorator?: ReactNode; // Content before label
469
- endDecorator?: ReactNode; // Content after label
728
+ endDecorator?: ReactNode; // Content after label
470
729
  }
471
730
 
472
731
  // Example option objects
@@ -491,10 +750,7 @@ const options = [
491
750
 
492
751
  ```tsx
493
752
  // Simplest usage with string array
494
- <Autocomplete
495
- label="Fruit"
496
- options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']}
497
- />
753
+ <Autocomplete label="Fruit" options={['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry']} />
498
754
  ```
499
755
 
500
756
  ### Controlled vs Uncontrolled
@@ -504,23 +760,12 @@ const options = [
504
760
  function ControlledExample() {
505
761
  const [value, setValue] = useState<string | undefined>();
506
762
 
507
- return (
508
- <Autocomplete
509
- value={value}
510
- onChange={(e) => setValue(e.target.value)}
511
- options={options}
512
- />
513
- );
763
+ return <Autocomplete value={value} onChange={(e) => setValue(e.target.value)} options={options} />;
514
764
  }
515
765
 
516
766
  // Uncontrolled - internal state management
517
767
  function UncontrolledExample() {
518
- return (
519
- <Autocomplete
520
- defaultValue="option-1"
521
- options={options}
522
- />
523
- );
768
+ return <Autocomplete defaultValue="option-1" options={options} />;
524
769
  }
525
770
  ```
526
771
 
@@ -554,12 +799,7 @@ function UncontrolledExample() {
554
799
  ### Multiple Selection
555
800
 
556
801
  ```tsx
557
- <Autocomplete
558
- multiple
559
- value={['option-1', 'option-2']}
560
- options={options}
561
- onChange={(e) => setValues(e.target.value)}
562
- />
802
+ <Autocomplete multiple value={['option-1', 'option-2']} options={options} onChange={(e) => setValues(e.target.value)} />
563
803
  ```
564
804
 
565
805
  ## Accessibility
@@ -578,7 +818,7 @@ Autocomplete includes comprehensive accessibility features:
578
818
  - **Arrow Down**: Open dropdown / move to next option
579
819
  - **Arrow Up**: Move to previous option
580
820
  - **Enter**: Select focused option
581
- - **Escape**: Close dropdown
821
+ - **Escape**: Close dropdown (clears value if `clearOnEscape` is enabled)
582
822
  - **Tab**: Move focus out of component
583
823
  - **Home**: Jump to first option
584
824
  - **End**: Jump to last option
@@ -589,7 +829,7 @@ Autocomplete includes comprehensive accessibility features:
589
829
  ```tsx
590
830
  // Proper labeling for screen readers
591
831
  <Autocomplete
592
- label="Select a country" // Announces: "Select a country, combobox"
832
+ label="Select a country" // Announces: "Select a country, combobox"
593
833
  placeholder="Search..."
594
834
  options={countries}
595
835
  />
@@ -603,6 +843,11 @@ Autocomplete includes comprehensive accessibility features:
603
843
  - Focus returns to input when selecting an option
604
844
  - Clear visual focus indicators on all interactive elements
605
845
 
846
+ ### Read Only vs Disabled Semantics
847
+
848
+ - **`readOnly`**: The input is focusable, its value can be copied, and screen readers announce it as read-only. Use when a value should be visible but not editable.
849
+ - **`disabled`**: The input is not focusable and is skipped in tab order. Use when the field is temporarily unavailable.
850
+
606
851
  ## Best Practices
607
852
 
608
853
  ### ✅ Do
@@ -611,32 +856,21 @@ Autocomplete includes comprehensive accessibility features:
611
856
 
612
857
  ```tsx
613
858
  // ✅ Good: Clear label and placeholder
614
- <Autocomplete
615
- label="Shipping Country"
616
- placeholder="Type to search countries..."
617
- options={countries}
618
- />
859
+ <Autocomplete label="Shipping Country" placeholder="Type to search countries..." options={countries} />
619
860
  ```
620
861
 
621
862
  2. **Show loading state during async operations**: Keep users informed
622
863
 
623
864
  ```tsx
624
865
  // ✅ Good: Loading indicator while fetching
625
- <Autocomplete
626
- options={options}
627
- loading={isLoading}
628
- placeholder={isLoading ? 'Loading...' : 'Search...'}
629
- />
866
+ <Autocomplete options={options} loading={isLoading} placeholder={isLoading ? 'Loading...' : 'Search...'} />
630
867
  ```
631
868
 
632
869
  3. **Use grouping for better organization**: Help users scan large lists
633
870
 
634
871
  ```tsx
635
872
  // ✅ Good: Logical grouping
636
- <Autocomplete
637
- options={allProducts}
638
- groupBy={(product) => product.category}
639
- />
873
+ <Autocomplete options={allProducts} groupBy={(product) => product.category} />
640
874
  ```
641
875
 
642
876
  4. **Debounce API searches**: Prevent excessive requests
@@ -649,6 +883,20 @@ useEffect(() => {
649
883
  }, [query]);
650
884
  ```
651
885
 
886
+ 5. **Use `disableCloseOnSelect` with `multiple`**: Keep the popup open for batch selection
887
+
888
+ ```tsx
889
+ // ✅ Good: Popup stays open for multiple selections
890
+ <Autocomplete multiple disableCloseOnSelect options={tags} />
891
+ ```
892
+
893
+ 6. **Set `limitTags` for constrained layouts**: Prevent tag overflow
894
+
895
+ ```tsx
896
+ // ✅ Good: Show at most 3 tags, truncate the rest
897
+ <Autocomplete multiple limitTags={3} options={allTags} />
898
+ ```
899
+
652
900
  ### ❌ Don't
653
901
 
654
902
  1. **Don't use for small option sets**: Use Select instead
@@ -686,20 +934,36 @@ useEffect(() => {
686
934
 
687
935
  ```tsx
688
936
  // ✅ Good: Handle empty results
689
- <Autocomplete
690
- options={filteredOptions}
691
- noOptionsText="No matches found. Try a different search."
692
- />
937
+ <Autocomplete options={filteredOptions} noOptionsText="No matches found. Try a different search." />
693
938
  ```
694
939
 
695
940
  4. **Don't block interaction during initial load**: Show placeholders
696
941
 
697
942
  ```tsx
698
943
  // ❌ Bad: Blocking the entire form
699
- {loading ? <Spinner /> : <Autocomplete options={options} />}
944
+ {
945
+ loading ? <Spinner /> : <Autocomplete options={options} />;
946
+ }
700
947
 
701
948
  // ✅ Good: Allow interaction with loading state
702
- <Autocomplete options={options} loading={loading} />
949
+ <Autocomplete options={options} loading={loading} />;
950
+ ```
951
+
952
+ 5. **Don't rely on `freeSolo` for validated input without extra handling**: Validate free-text values
953
+
954
+ ```tsx
955
+ // ❌ Bad: No validation for free-text input
956
+ <Autocomplete freeSolo options={emails} />
957
+
958
+ // ✅ Good: Validate free-text input via onInputChange
959
+ <Autocomplete
960
+ freeSolo
961
+ options={emails}
962
+ onInputChange={(e) => {
963
+ const value = e.target.value;
964
+ setError(!isValidEmail(value));
965
+ }}
966
+ />
703
967
  ```
704
968
 
705
969
  ## Performance Considerations
@@ -710,9 +974,7 @@ Autocomplete automatically virtualizes long lists for performance:
710
974
 
711
975
  ```tsx
712
976
  // Handles 1000+ options efficiently
713
- <Autocomplete
714
- options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)}
715
- />
977
+ <Autocomplete options={Array.from({ length: 1000 }, (_, i) => `Option ${i + 1}`)} />
716
978
  ```
717
979
 
718
980
  ### Debounce Search Requests
@@ -739,16 +1001,10 @@ function SearchAutocomplete({ fetchOptions }) {
739
1001
  setLoading(false);
740
1002
  }
741
1003
  }, 300),
742
- [fetchOptions]
1004
+ [fetchOptions],
743
1005
  );
744
1006
 
745
- return (
746
- <Autocomplete
747
- options={options}
748
- loading={loading}
749
- onInputChange={(e) => debouncedFetch(e.target.value)}
750
- />
751
- );
1007
+ return <Autocomplete options={options} loading={loading} onInputChange={(e) => debouncedFetch(e.target.value)} />;
752
1008
  }
753
1009
  ```
754
1010
 
@@ -764,10 +1020,10 @@ const options = useMemo(
764
1020
  label: item.name,
765
1021
  startDecorator: <StatusChip status={item.status} />,
766
1022
  })),
767
- [data]
1023
+ [data],
768
1024
  );
769
1025
 
770
- <Autocomplete options={options} />
1026
+ <Autocomplete options={options} />;
771
1027
  ```
772
1028
 
773
1029
  ### Lazy Load Options
@@ -793,14 +1049,7 @@ function LazyLoadAutocomplete() {
793
1049
  }
794
1050
  };
795
1051
 
796
- return (
797
- <Autocomplete
798
- options={options}
799
- loading={loading}
800
- onFocus={loadOptions}
801
- onOpen={loadOptions}
802
- />
803
- );
1052
+ return <Autocomplete options={options} loading={loading} onFocus={loadOptions} onOpen={loadOptions} />;
804
1053
  }
805
1054
  ```
806
1055
 
@@ -828,7 +1077,7 @@ const options = useMemo(
828
1077
  label: user.name,
829
1078
  startDecorator: <MemoizedOption option={user} />,
830
1079
  })),
831
- [users]
1080
+ [users],
832
1081
  );
833
1082
  ```
834
1083