@ceed/cds 1.28.0 → 1.29.0-next.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.
Files changed (65) hide show
  1. package/dist/components/CurrencyInput/CurrencyInput.d.ts +1 -1
  2. package/dist/components/CurrencyInput/hooks/use-currency-setting.d.ts +2 -2
  3. package/dist/components/DataTable/hooks.d.ts +2 -1
  4. package/dist/components/DataTable/utils.d.ts +1 -0
  5. package/dist/components/SearchBar/SearchBar.d.ts +21 -0
  6. package/dist/components/SearchBar/index.d.ts +3 -0
  7. package/dist/components/data-display/DataTable.md +1 -1
  8. package/dist/components/data-display/InfoSign.md +91 -74
  9. package/dist/components/data-display/Typography.md +94 -411
  10. package/dist/components/feedback/Dialog.md +62 -76
  11. package/dist/components/feedback/Modal.md +138 -430
  12. package/dist/components/feedback/llms.txt +0 -2
  13. package/dist/components/index.d.ts +2 -1
  14. package/dist/components/inputs/Autocomplete.md +107 -356
  15. package/dist/components/inputs/ButtonGroup.md +104 -115
  16. package/dist/components/inputs/CurrencyInput.md +5 -183
  17. package/dist/components/inputs/DatePicker.md +431 -108
  18. package/dist/components/inputs/DateRangePicker.md +492 -131
  19. package/dist/components/inputs/FilterableCheckboxGroup.md +19 -145
  20. package/dist/components/inputs/IconButton.md +88 -137
  21. package/dist/components/inputs/Input.md +73 -204
  22. package/dist/components/inputs/MonthPicker.md +422 -95
  23. package/dist/components/inputs/MonthRangePicker.md +466 -89
  24. package/dist/components/inputs/PercentageInput.md +16 -185
  25. package/dist/components/inputs/RadioButton.md +35 -163
  26. package/dist/components/inputs/SearchBar.md +44 -0
  27. package/dist/components/inputs/Select.md +326 -222
  28. package/dist/components/inputs/Switch.md +376 -143
  29. package/dist/components/inputs/Textarea.md +10 -213
  30. package/dist/components/inputs/Uploader/Uploader.md +66 -145
  31. package/dist/components/inputs/llms.txt +1 -4
  32. package/dist/components/navigation/Breadcrumbs.md +308 -57
  33. package/dist/components/navigation/Drawer.md +0 -180
  34. package/dist/components/navigation/Dropdown.md +215 -98
  35. package/dist/components/navigation/IconMenuButton.md +502 -40
  36. package/dist/components/navigation/InsetDrawer.md +650 -281
  37. package/dist/components/navigation/Link.md +348 -31
  38. package/dist/components/navigation/Menu.md +285 -92
  39. package/dist/components/navigation/MenuButton.md +448 -55
  40. package/dist/components/navigation/Pagination.md +338 -47
  41. package/dist/components/navigation/Stepper.md +28 -160
  42. package/dist/components/navigation/Tabs.md +316 -57
  43. package/dist/components/surfaces/Accordions.md +804 -49
  44. package/dist/components/surfaces/Card.md +157 -97
  45. package/dist/components/surfaces/Divider.md +234 -83
  46. package/dist/components/surfaces/Sheet.md +328 -153
  47. package/dist/index.cjs +435 -577
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +424 -510
  50. package/dist/llms.txt +1 -9
  51. package/framer/index.js +1 -1
  52. package/package.json +17 -22
  53. package/dist/chunks/rehype-accent-FZRUD7VI.js +0 -39
  54. package/dist/components/RadioTileGroup/RadioTileGroup.d.ts +0 -56
  55. package/dist/components/RadioTileGroup/index.d.ts +0 -3
  56. package/dist/components/feedback/CircularProgress.md +0 -257
  57. package/dist/components/feedback/Skeleton.md +0 -280
  58. package/dist/components/inputs/FormControl.md +0 -361
  59. package/dist/components/inputs/RadioList.md +0 -241
  60. package/dist/components/inputs/RadioTileGroup.md +0 -507
  61. package/dist/components/inputs/Slider.md +0 -334
  62. package/dist/guides/ThemeProvider.md +0 -89
  63. package/dist/guides/llms.txt +0 -9
  64. package/dist/index.browser.js +0 -224
  65. package/dist/index.browser.js.map +0 -7
@@ -1,280 +0,0 @@
1
- # Skeleton
2
-
3
- ## Introduction
4
-
5
- The Skeleton component provides placeholder previews of content before data is loaded. It is based on Joy UI's Skeleton and helps reduce perceived loading time by showing an approximation of the page layout. Skeletons improve the user experience by preventing layout shifts and giving users a visual cue that content is on its way.
6
-
7
- ```tsx
8
- <Skeleton
9
- variant="rectangular"
10
- width={200}
11
- height={24}
12
- />
13
- ```
14
-
15
- | Field | Description | Default |
16
- | ---------------------------- | ----------- | ------- |
17
- | Controls resolved at runtime | — | — |
18
-
19
- ## Usage
20
-
21
- ```tsx
22
- import { Skeleton } from '@ceed/cds';
23
-
24
- function MyComponent() {
25
- return <Skeleton variant="rectangular" width={200} height={24} />;
26
- }
27
- ```
28
-
29
- ## Variants
30
-
31
- Skeleton supports three variants: `rectangular`, `circular`, and `text`.
32
-
33
- - **rectangular**: Block-shaped placeholder for images, cards, and content areas.
34
- - **circular**: Round placeholder for avatars and icons.
35
- - **text**: Matches the height and spacing of text content at a given `level`.
36
-
37
- ```tsx
38
- <>
39
- <Skeleton variant="rectangular" width={200} height={24} />
40
- <Skeleton variant="circular" width={48} height={48} />
41
- <Skeleton variant="text" width={200} />
42
- </>
43
- ```
44
-
45
- ```tsx
46
- <Skeleton variant="rectangular" width={200} height={24} />
47
- <Skeleton variant="circular" width={48} height={48} />
48
- <Skeleton variant="text" width={200} />
49
- ```
50
-
51
- ## Animations
52
-
53
- Skeleton supports `wave` (default) and `pulse` animations. Set `animation={false}` to disable animation entirely.
54
-
55
- ```tsx
56
- <Stack gap={3}>
57
- <Box>
58
- <Typography level="body-sm" sx={{
59
- mb: 1
60
- }}>
61
- Wave (default)
62
- </Typography>
63
- <Skeleton animation="wave" variant="rectangular" width={200} height={24} />
64
- </Box>
65
- <Box>
66
- <Typography level="body-sm" sx={{
67
- mb: 1
68
- }}>
69
- Pulse
70
- </Typography>
71
- <Skeleton animation="pulse" variant="rectangular" width={200} height={24} />
72
- </Box>
73
- <Box>
74
- <Typography level="body-sm" sx={{
75
- mb: 1
76
- }}>
77
- No animation (false)
78
- </Typography>
79
- <Skeleton animation={false} variant="rectangular" width={200} height={24} />
80
- </Box>
81
- </Stack>
82
- ```
83
-
84
- ```tsx
85
- <Skeleton animation="wave" variant="rectangular" width={200} height={24} />
86
- <Skeleton animation="pulse" variant="rectangular" width={200} height={24} />
87
- <Skeleton animation={false} variant="rectangular" width={200} height={24} />
88
- ```
89
-
90
- ## Text Skeleton
91
-
92
- Use `variant="text"` with the `level` prop to match Typography sizing. This is useful for creating text content placeholders that accurately reflect the final layout.
93
-
94
- ```tsx
95
- <Stack gap={1} sx={{
96
- width: 300
97
- }}>
98
- <Skeleton variant="text" level="h3" />
99
- <Skeleton variant="text" level="body-md" />
100
- <Skeleton variant="text" level="body-md" />
101
- <Skeleton variant="text" level="body-md" width="80%" />
102
- </Stack>
103
- ```
104
-
105
- ```tsx
106
- <Skeleton variant="text" level="h3" />
107
- <Skeleton variant="text" level="body-md" />
108
- <Skeleton variant="text" level="body-md" />
109
- <Skeleton variant="text" level="body-md" width="80%" />
110
- ```
111
-
112
- ## Card Skeleton
113
-
114
- Compose multiple Skeleton elements to create placeholder layouts for complex components like cards.
115
-
116
- ```tsx
117
- <Box sx={{
118
- width: 300,
119
- p: 2,
120
- border: '1px solid',
121
- borderColor: 'divider',
122
- borderRadius: 'sm'
123
- }}>
124
- <Skeleton variant="rectangular" width="100%" height={140} sx={{
125
- borderRadius: 'sm',
126
- mb: 2
127
- }} />
128
- <Skeleton variant="text" level="title-md" sx={{
129
- mb: 1
130
- }} />
131
- <Skeleton variant="text" level="body-sm" />
132
- <Skeleton variant="text" level="body-sm" width="60%" />
133
- <Stack direction="row" gap={1} sx={{
134
- mt: 2
135
- }}>
136
- <Skeleton variant="rectangular" width={80} height={32} sx={{
137
- borderRadius: 'sm'
138
- }} />
139
- <Skeleton variant="rectangular" width={80} height={32} sx={{
140
- borderRadius: 'sm'
141
- }} />
142
- </Stack>
143
- </Box>
144
- ```
145
-
146
- ## Data Loading List
147
-
148
- Combine circular and text skeletons to represent list items during loading.
149
-
150
- ```tsx
151
- <Stack gap={2} sx={{
152
- width: 400
153
- }}>
154
- {[1, 2, 3].map(i => <Stack key={i} direction="row" gap={2} alignItems="center">
155
- <Skeleton variant="circular" width={40} height={40} />
156
- <Box sx={{
157
- flex: 1
158
- }}>
159
- <Skeleton variant="text" level="title-sm" width="60%" />
160
- <Skeleton variant="text" level="body-xs" width="40%" />
161
- </Box>
162
- </Stack>)}
163
- </Stack>
164
- ```
165
-
166
- ## Inline Wrapping
167
-
168
- Wrap existing content with Skeleton to overlay it while loading. Set the `loading` prop to control visibility.
169
-
170
- ```tsx
171
- <Typography level="body-md">
172
- <Skeleton loading>
173
- This text will be hidden behind a skeleton while loading.
174
- </Skeleton>
175
- </Typography>
176
- ```
177
-
178
- ```tsx
179
- <Typography level="body-md">
180
- <Skeleton loading>
181
- This text will be hidden behind a skeleton while loading.
182
- </Skeleton>
183
- </Typography>
184
- ```
185
-
186
- ## Common Use Cases
187
-
188
- ### Page Content Loading
189
-
190
- ```tsx
191
- function PageSkeleton() {
192
- return (
193
- <Stack gap={3}>
194
- <Skeleton variant="text" level="h1" width="50%" />
195
- <Skeleton variant="text" level="body-md" />
196
- <Skeleton variant="text" level="body-md" />
197
- <Skeleton variant="text" level="body-md" width="75%" />
198
-
199
- <Skeleton variant="rectangular" width="100%" height={200} sx={{ borderRadius: 'sm' }} />
200
-
201
- <Skeleton variant="text" level="body-md" />
202
- <Skeleton variant="text" level="body-md" />
203
- </Stack>
204
- );
205
- }
206
- ```
207
-
208
- ### User List Loading
209
-
210
- ```tsx
211
- function UserListSkeleton({ count = 5 }: { count?: number }) {
212
- return (
213
- <Stack gap={2}>
214
- {Array.from({ length: count }).map((_, i) => (
215
- <Stack key={i} direction="row" gap={2} alignItems="center">
216
- <Skeleton variant="circular" width={40} height={40} />
217
- <Box sx={{ flex: 1 }}>
218
- <Skeleton variant="text" level="title-sm" width="40%" />
219
- <Skeleton variant="text" level="body-xs" width="25%" />
220
- </Box>
221
- </Stack>
222
- ))}
223
- </Stack>
224
- );
225
- }
226
- ```
227
-
228
- ### Conditional Rendering
229
-
230
- ```tsx
231
- function UserProfile({ loading, user }: { loading: boolean; user?: User }) {
232
- return (
233
- <Stack direction="row" gap={2} alignItems="center">
234
- {loading ? (
235
- <Skeleton variant="circular" width={48} height={48} />
236
- ) : (
237
- <Avatar src={user?.avatar} />
238
- )}
239
- <Box>
240
- <Typography level="title-md">
241
- <Skeleton loading={loading}>{user?.name || 'Placeholder Name'}</Skeleton>
242
- </Typography>
243
- <Typography level="body-sm">
244
- <Skeleton loading={loading}>{user?.email || 'email@example.com'}</Skeleton>
245
- </Typography>
246
- </Box>
247
- </Stack>
248
- );
249
- }
250
- ```
251
-
252
- ## Best Practices
253
-
254
- 1. **Match the final layout**: Skeleton placeholders should closely approximate the size and position of the real content to prevent layout shifts.
255
-
256
- ```tsx
257
- // ✅ Matches the actual content structure
258
- <Stack gap={1}>
259
- <Skeleton variant="text" level="title-md" width="60%" />
260
- <Skeleton variant="text" level="body-sm" />
261
- </Stack>
262
-
263
- // ❌ Generic rectangle that doesn't match
264
- <Skeleton variant="rectangular" width={300} height={100} />
265
- ```
266
-
267
- 2. **Use `variant="text"` with `level`**: When replacing Typography, use the text variant with the matching level to get accurate line heights.
268
-
269
- 3. **Avoid over-skeletonizing**: Only skeleton the main content areas. Don't add skeletons for static elements like navigation or headers that are always present.
270
-
271
- 4. **Use consistent animation**: Keep the same animation type (`wave` or `pulse`) across the entire application for a cohesive loading experience.
272
-
273
- 5. **Set appropriate widths**: Vary skeleton widths (e.g., 60%, 80%, 100%) to mimic natural text line lengths rather than using uniform widths.
274
-
275
- ## Accessibility
276
-
277
- - Skeleton elements are purely decorative. Screen readers should focus on the loading state announcement, not individual skeleton elements.
278
- - Use `aria-busy="true"` on the container element while content is loading.
279
- - Provide an `aria-label` or visually hidden text that describes the loading state (e.g., "Loading user profile").
280
- - Ensure the animation respects `prefers-reduced-motion` — Joy UI handles this automatically.
@@ -1,361 +0,0 @@
1
- # FormControl
2
-
3
- ## Introduction
4
-
5
- FormControl is a wrapper component that provides context to form elements such as Input, Textarea, Select, and more. It manages shared states like `error`, `disabled`, `required`, and `size`, passing them down to its children automatically. Use it with FormLabel and FormHelperText to build accessible, consistent form fields.
6
-
7
- ```tsx
8
- <FormControl {...args}>
9
- <FormLabel>Label</FormLabel>
10
- <Input placeholder="Enter text…" />
11
- <FormHelperText>This is helper text.</FormHelperText>
12
- </FormControl>
13
- ```
14
-
15
- | Field | Description | Default |
16
- | ----------- | ----------- | ------- |
17
- | size | — | — |
18
- | error | — | — |
19
- | disabled | — | — |
20
- | required | — | — |
21
- | orientation | — | — |
22
-
23
- ## Usage
24
-
25
- ```tsx
26
- import { FormControl, FormLabel, FormHelperText, Input } from '@ceed/cds';
27
-
28
- function MyForm() {
29
- return (
30
- <FormControl>
31
- <FormLabel>Username</FormLabel>
32
- <Input placeholder="Enter username" />
33
- <FormHelperText>Choose a unique username.</FormHelperText>
34
- </FormControl>
35
- );
36
- }
37
- ```
38
-
39
- ## With Input
40
-
41
- The most common pattern — wrapping an Input with a label and helper text.
42
-
43
- ```tsx
44
- <FormControl>
45
- <FormLabel>Username</FormLabel>
46
- <Input placeholder="Enter username" />
47
- <FormHelperText>Choose a unique username.</FormHelperText>
48
- </FormControl>
49
- ```
50
-
51
- ## With Textarea
52
-
53
- FormControl works equally well with Textarea components.
54
-
55
- ```tsx
56
- <FormControl>
57
- <FormLabel>Description</FormLabel>
58
- <Textarea placeholder="Enter description…" minRows={3} />
59
- <FormHelperText>Provide a detailed description.</FormHelperText>
60
- </FormControl>
61
- ```
62
-
63
- ## With Select
64
-
65
- Use FormControl with Select for dropdown fields.
66
-
67
- ```tsx
68
- <FormControl>
69
- <FormLabel>Role</FormLabel>
70
- <Select placeholder="Select a role" options={roleOptions} />
71
- <FormHelperText>Select the user role.</FormHelperText>
72
- </FormControl>
73
- ```
74
-
75
- ## Error State
76
-
77
- Set `error` on FormControl to propagate the error state to all child components. FormHelperText automatically changes color to indicate the error.
78
-
79
- ```tsx
80
- <FormControl error>
81
- <FormLabel>Email</FormLabel>
82
- <Input placeholder="email@example.com" defaultValue="invalid-email" />
83
- <FormHelperText>Please enter a valid email address.</FormHelperText>
84
- </FormControl>
85
- ```
86
-
87
- ```tsx
88
- <FormControl error>
89
- <FormLabel>Email</FormLabel>
90
- <Input placeholder="email@example.com" value="invalid-email" />
91
- <FormHelperText>Please enter a valid email address.</FormHelperText>
92
- </FormControl>
93
- ```
94
-
95
- ## Disabled State
96
-
97
- Set `disabled` to disable all child form elements at once.
98
-
99
- ```tsx
100
- <FormControl disabled>
101
- <FormLabel>Name</FormLabel>
102
- <Input placeholder="Enter name" defaultValue="John Doe" />
103
- <FormHelperText>This field is disabled.</FormHelperText>
104
- </FormControl>
105
- ```
106
-
107
- ```tsx
108
- <FormControl disabled>
109
- <FormLabel>Name</FormLabel>
110
- <Input placeholder="Enter name" value="John Doe" />
111
- <FormHelperText>This field is disabled.</FormHelperText>
112
- </FormControl>
113
- ```
114
-
115
- ## Required Field
116
-
117
- Set `required` to add an asterisk (\*) to the label and mark the field as required.
118
-
119
- ```tsx
120
- <FormControl required>
121
- <FormLabel>Full Name</FormLabel>
122
- <Input placeholder="Enter your full name" />
123
- <FormHelperText>This field is required.</FormHelperText>
124
- </FormControl>
125
- ```
126
-
127
- ```tsx
128
- <FormControl required>
129
- <FormLabel>Full Name</FormLabel>
130
- <Input placeholder="Enter your full name" />
131
- </FormControl>
132
- ```
133
-
134
- ## Sizes
135
-
136
- FormControl supports `sm`, `md`, and `lg` sizes. The size is inherited by child components.
137
-
138
- ```tsx
139
- <Stack gap={3}>
140
- <FormControl size="sm">
141
- <FormLabel>Small</FormLabel>
142
- <Input placeholder="Small input" />
143
- </FormControl>
144
- <FormControl size="md">
145
- <FormLabel>Medium</FormLabel>
146
- <Input placeholder="Medium input" />
147
- </FormControl>
148
- <FormControl size="lg">
149
- <FormLabel>Large</FormLabel>
150
- <Input placeholder="Large input" />
151
- </FormControl>
152
- </Stack>
153
- ```
154
-
155
- ## Horizontal Layout
156
-
157
- Use `orientation="horizontal"` for inline form controls, such as Switch or Checkbox toggles.
158
-
159
- ```tsx
160
- <FormControl orientation="horizontal" sx={{
161
- gap: 2
162
- }}>
163
- <FormLabel>Subscribe</FormLabel>
164
- <Switch />
165
- </FormControl>
166
- ```
167
-
168
- ```tsx
169
- <FormControl orientation="horizontal" sx={{ gap: 2 }}>
170
- <FormLabel>Subscribe</FormLabel>
171
- <Switch />
172
- </FormControl>
173
- ```
174
-
175
- ## Form Example
176
-
177
- A complete form demonstrating FormControl with multiple input types.
178
-
179
- ```tsx
180
- <Stack gap={2} sx={{
181
- maxWidth: 400
182
- }}>
183
- <FormControl required>
184
- <FormLabel>Name</FormLabel>
185
- <Input placeholder="Enter your name" />
186
- </FormControl>
187
- <FormControl required>
188
- <FormLabel>Email</FormLabel>
189
- <Input type="email" placeholder="email@example.com" />
190
- <FormHelperText>We will never share your email.</FormHelperText>
191
- </FormControl>
192
- <FormControl>
193
- <FormLabel>Role</FormLabel>
194
- <Select placeholder="Select a role" options={roleOptions} />
195
- </FormControl>
196
- <FormControl>
197
- <FormLabel>Bio</FormLabel>
198
- <Textarea placeholder="Tell us about yourself…" minRows={3} />
199
- <FormHelperText>Maximum 500 characters.</FormHelperText>
200
- </FormControl>
201
- </Stack>
202
- ```
203
-
204
- ## FormLabel
205
-
206
- FormLabel renders a `<label>` element associated with its sibling input. It automatically displays an asterisk when the parent FormControl has `required` set.
207
-
208
- ```tsx
209
- <FormControl required>
210
- <FormLabel>Email</FormLabel>
211
- <Input />
212
- </FormControl>
213
- ```
214
-
215
- ## FormHelperText
216
-
217
- FormHelperText provides supplementary guidance below the input. It automatically switches to the error color when the parent FormControl has `error` set.
218
-
219
- ```tsx
220
- <FormControl error>
221
- <FormLabel>Password</FormLabel>
222
- <Input type="password" />
223
- <FormHelperText>Password must be at least 8 characters.</FormHelperText>
224
- </FormControl>
225
- ```
226
-
227
- ## Common Use Cases
228
-
229
- ### Registration Form
230
-
231
- ```tsx
232
- function RegistrationForm() {
233
- const [errors, setErrors] = React.useState({});
234
-
235
- return (
236
- <Stack gap={2} component="form">
237
- <FormControl required error={!!errors.name}>
238
- <FormLabel>Name</FormLabel>
239
- <Input placeholder="Enter your name" />
240
- {errors.name && <FormHelperText>{errors.name}</FormHelperText>}
241
- </FormControl>
242
-
243
- <FormControl required error={!!errors.email}>
244
- <FormLabel>Email</FormLabel>
245
- <Input type="email" placeholder="email@example.com" />
246
- {errors.email && <FormHelperText>{errors.email}</FormHelperText>}
247
- </FormControl>
248
-
249
- <FormControl required error={!!errors.password}>
250
- <FormLabel>Password</FormLabel>
251
- <Input type="password" placeholder="Enter password" />
252
- <FormHelperText>{errors.password || 'Must be at least 8 characters.'}</FormHelperText>
253
- </FormControl>
254
-
255
- <FormControl orientation="horizontal">
256
- <Checkbox label="I agree to the terms and conditions" />
257
- </FormControl>
258
- </Stack>
259
- );
260
- }
261
- ```
262
-
263
- ### Settings Form with Mixed Controls
264
-
265
- ```tsx
266
- function SettingsForm() {
267
- return (
268
- <Stack gap={2}>
269
- <FormControl orientation="horizontal" sx={{ justifyContent: 'space-between' }}>
270
- <FormLabel>Email notifications</FormLabel>
271
- <Switch />
272
- </FormControl>
273
-
274
- <FormControl>
275
- <FormLabel>Language</FormLabel>
276
- <Select
277
- defaultValue="en"
278
- options={[
279
- { value: 'en', label: 'English' },
280
- { value: 'ko', label: 'Korean' },
281
- ]}
282
- />
283
- </FormControl>
284
-
285
- <FormControl>
286
- <FormLabel>Notes</FormLabel>
287
- <Textarea minRows={3} placeholder="Additional notes…" />
288
- <FormHelperText>Optional</FormHelperText>
289
- </FormControl>
290
- </Stack>
291
- );
292
- }
293
- ```
294
-
295
- > **Tip: Use built-in form props instead**
296
- >
297
- > Input, Select, Textarea, DatePicker 등 Input 계열 컴포넌트는 `label`, `helperText` prop을 자체적으로 지원합니다.
298
- > 간단한 form 필드에는 각 컴포넌트의 내장 prop을 사용하는 것이 더 간결합니다.
299
- >
300
- > ```tsx
301
- > // ✅ Simpler: built-in props
302
- > <Input label="Username" helperText="Choose a unique username." />
303
- >
304
- > // ⬆️ Equivalent to:
305
- > <FormControl>
306
- > <FormLabel>Username</FormLabel>
307
- > <Input placeholder="Enter username" />
308
- > <FormHelperText>Choose a unique username.</FormHelperText>
309
- > </FormControl>
310
- > ```
311
- >
312
- > FormControl is most useful when you need to compose multiple elements, share state across siblings, or use horizontal orientation.
313
-
314
- ## Best Practices
315
-
316
- 1. **Always pair with FormLabel**: Every form field should have a label for accessibility. Use FormLabel inside FormControl.
317
-
318
- ```tsx
319
- // ✅ Accessible — label is associated with input
320
- <FormControl>
321
- <FormLabel>Email</FormLabel>
322
- <Input />
323
- </FormControl>
324
-
325
- // ❌ No label — screen readers cannot identify the input
326
- <FormControl>
327
- <Input placeholder="Email" />
328
- </FormControl>
329
- ```
330
-
331
- 2. **Use `error` prop on FormControl, not children**: Set error state on FormControl so all children react consistently.
332
-
333
- ```tsx
334
- // ✅ Error propagated from FormControl
335
- <FormControl error>
336
- <FormLabel>Email</FormLabel>
337
- <Input />
338
- <FormHelperText>Invalid email</FormHelperText>
339
- </FormControl>
340
-
341
- // ❌ Inconsistent — only Input shows error
342
- <FormControl>
343
- <FormLabel>Email</FormLabel>
344
- <Input error />
345
- <FormHelperText>Invalid email</FormHelperText>
346
- </FormControl>
347
- ```
348
-
349
- 3. **Use consistent sizing**: Set `size` on FormControl to ensure all children (label, input, helper text) are proportionally sized.
350
-
351
- 4. **Mark required fields**: Use the `required` prop to automatically add visual indicators and `aria-required` attributes.
352
-
353
- 5. **Provide helpful error messages**: When using the error state, always include a FormHelperText explaining what went wrong and how to fix it.
354
-
355
- ## Accessibility
356
-
357
- - FormControl automatically associates FormLabel with the input using `htmlFor` and `id`.
358
- - The `required` prop adds `aria-required="true"` to the input and an asterisk to the label.
359
- - The `error` prop adds `aria-invalid="true"` to the input.
360
- - FormHelperText is linked to the input via `aria-describedby` so screen readers announce it.
361
- - Use the `disabled` prop on FormControl to set `aria-disabled` on child elements.