@ceed/cds 1.24.1-next.3 → 1.25.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 (63) 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/styled.d.ts +3 -1
  6. package/dist/components/DataTable/types.d.ts +1 -0
  7. package/dist/components/RadioTileGroup/RadioTileGroup.d.ts +56 -0
  8. package/dist/components/RadioTileGroup/index.d.ts +3 -0
  9. package/dist/components/data-display/DataTable.md +77 -1
  10. package/dist/components/data-display/InfoSign.md +74 -91
  11. package/dist/components/data-display/Typography.md +411 -94
  12. package/dist/components/feedback/CircularProgress.md +257 -0
  13. package/dist/components/feedback/Dialog.md +76 -62
  14. package/dist/components/feedback/Modal.md +430 -138
  15. package/dist/components/feedback/Skeleton.md +280 -0
  16. package/dist/components/feedback/llms.txt +2 -0
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/inputs/Autocomplete.md +356 -107
  19. package/dist/components/inputs/ButtonGroup.md +115 -104
  20. package/dist/components/inputs/CurrencyInput.md +183 -5
  21. package/dist/components/inputs/DatePicker.md +108 -431
  22. package/dist/components/inputs/DateRangePicker.md +131 -492
  23. package/dist/components/inputs/FilterableCheckboxGroup.md +145 -19
  24. package/dist/components/inputs/FormControl.md +361 -0
  25. package/dist/components/inputs/IconButton.md +137 -88
  26. package/dist/components/inputs/Input.md +204 -73
  27. package/dist/components/inputs/MonthPicker.md +95 -422
  28. package/dist/components/inputs/MonthRangePicker.md +89 -466
  29. package/dist/components/inputs/PercentageInput.md +185 -16
  30. package/dist/components/inputs/RadioButton.md +163 -35
  31. package/dist/components/inputs/RadioList.md +241 -0
  32. package/dist/components/inputs/RadioTileGroup.md +507 -0
  33. package/dist/components/inputs/Select.md +222 -326
  34. package/dist/components/inputs/Slider.md +334 -0
  35. package/dist/components/inputs/Switch.md +143 -376
  36. package/dist/components/inputs/Textarea.md +213 -10
  37. package/dist/components/inputs/Uploader/Uploader.md +145 -66
  38. package/dist/components/inputs/llms.txt +4 -0
  39. package/dist/components/navigation/Breadcrumbs.md +57 -308
  40. package/dist/components/navigation/Drawer.md +180 -0
  41. package/dist/components/navigation/Dropdown.md +98 -215
  42. package/dist/components/navigation/IconMenuButton.md +40 -502
  43. package/dist/components/navigation/InsetDrawer.md +281 -650
  44. package/dist/components/navigation/Link.md +31 -348
  45. package/dist/components/navigation/Menu.md +92 -285
  46. package/dist/components/navigation/MenuButton.md +55 -448
  47. package/dist/components/navigation/Pagination.md +47 -338
  48. package/dist/components/navigation/Stepper.md +160 -28
  49. package/dist/components/navigation/Tabs.md +57 -316
  50. package/dist/components/surfaces/Accordions.md +49 -804
  51. package/dist/components/surfaces/Card.md +97 -157
  52. package/dist/components/surfaces/Divider.md +83 -234
  53. package/dist/components/surfaces/Sheet.md +153 -328
  54. package/dist/guides/ThemeProvider.md +89 -0
  55. package/dist/guides/llms.txt +9 -0
  56. package/dist/index.browser.js +224 -0
  57. package/dist/index.browser.js.map +7 -0
  58. package/dist/index.cjs +648 -390
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.js +563 -361
  61. package/dist/llms.txt +9 -0
  62. package/framer/index.js +1 -163
  63. package/package.json +22 -17
@@ -2,7 +2,14 @@
2
2
 
3
3
  ## Introduction
4
4
 
5
- FilterableCheckboxGroup 컴포넌트는 대량의 옵션 중에서 여러 항목을 선택할 있는 검색 가능한 체크박스 그룹입니다. 검색 필터링과 가상 스크롤링을 통해 많은 수의 옵션을 효율적으로 처리할 있으며, "Select all" 기능으로 일괄 선택이 가능합니다. 필터링, 다중 선택 폼, 설정 화면 등에서 유용하게 사용됩니다.
5
+ FilterableCheckboxGroup is a multi-select component that combines a search input with a scrollable list of checkboxes. It is designed for scenarios where users need to select multiple items from a potentially large dataset. The built-in search filter narrows down visible options in real time, and virtual scrolling ensures smooth performance even with hundreds of items.
6
+
7
+ Key capabilities include a "Select all" toggle for bulk selection, automatic sorting (selected items first, then alphabetical), customizable list height, and built-in form integration via `label`, `helperText`, and `required` props. The component accepts options as either simple strings or `{ value, label }` objects.
8
+
9
+ > **Form 구성 시 내장 prop 사용을 권장합니다**
10
+ >
11
+ > 이 컴포넌트는 `label`, `helperText` 등의 Form 요소를 자체적으로 지원합니다.
12
+ > Form을 구성할 때 Typography로 별도의 label이나 helperText를 만드는 대신, 컴포넌트의 내장 prop을 사용하세요.
6
13
 
7
14
  ```tsx
8
15
  <FilterableCheckboxGroup
@@ -27,7 +34,7 @@ FilterableCheckboxGroup 컴포넌트는 대량의 옵션 중에서 여러 항목
27
34
  ## Usage
28
35
 
29
36
  ```tsx
30
- import { FilterableCheckboxGroup } from '@ceed/ads';
37
+ import { FilterableCheckboxGroup } from '@ceed/cds';
31
38
 
32
39
  function MyComponent() {
33
40
  const [selectedValues, setSelectedValues] = useState<string[]>([]);
@@ -50,11 +57,9 @@ function MyComponent() {
50
57
  }
51
58
  ```
52
59
 
53
- ## Examples
60
+ ## Sizes
54
61
 
55
- ### Sizes
56
-
57
- 다양한 크기의 FilterableCheckboxGroup을 사용할 수 있습니다.
62
+ Three sizes are available: `sm`, `md`, and `lg`. The size affects the search input, checkboxes, and overall spacing.
58
63
 
59
64
  ```tsx
60
65
  <Stack spacing={3}>
@@ -64,9 +69,11 @@ function MyComponent() {
64
69
  </Stack>
65
70
  ```
66
71
 
72
+ ## Label and Helper Text
73
+
67
74
  ### With Label
68
75
 
69
- 라벨을 추가할 있습니다.
76
+ Use the `label` prop to describe the purpose of the checkbox group.
70
77
 
71
78
  ```tsx
72
79
  <FilterableCheckboxGroup
@@ -78,7 +85,7 @@ function MyComponent() {
78
85
 
79
86
  ### With Helper Text
80
87
 
81
- 도움말 텍스트를 추가할 있습니다.
88
+ Add a `helperText` prop to provide additional guidance below the component.
82
89
 
83
90
  ```tsx
84
91
  <FilterableCheckboxGroup
@@ -91,7 +98,7 @@ function MyComponent() {
91
98
 
92
99
  ### Required Field
93
100
 
94
- 필수 입력 필드로 표시할 있습니다.
101
+ Set `required` to append a required indicator to the label.
95
102
 
96
103
  ```tsx
97
104
  <FilterableCheckboxGroup
@@ -103,9 +110,9 @@ function MyComponent() {
103
110
  />
104
111
  ```
105
112
 
106
- ### Custom Max Height
113
+ ## Custom Max Height
107
114
 
108
- 목록의 최대 높이를 커스터마이징할 있습니다.
115
+ Control the visible height of the options list with the `maxHeight` prop (default: 300px). A smaller height is useful when screen space is limited.
109
116
 
110
117
  ```tsx
111
118
  <Stack spacing={3}>
@@ -114,9 +121,9 @@ function MyComponent() {
114
121
  </Stack>
115
122
  ```
116
123
 
117
- ### Long List
124
+ ## Long List with Virtual Scrolling
118
125
 
119
- 많은 수의 옵션을 가상 스크롤링으로 효율적으로 표시합니다.
126
+ When the options list is large, the component uses virtual scrolling to render only the visible items. This keeps the UI performant regardless of the total number of options.
120
127
 
121
128
  ```tsx
122
129
  <FilterableCheckboxGroup
@@ -129,9 +136,9 @@ function MyComponent() {
129
136
  />
130
137
  ```
131
138
 
132
- ### No Options
139
+ ## Empty State
133
140
 
134
- 옵션이 없을 때의 상태입니다.
141
+ When no options are provided, the component displays an empty state. This is useful for dynamically loaded option lists that may start empty.
135
142
 
136
143
  ```tsx
137
144
  <FilterableCheckboxGroup
@@ -141,9 +148,9 @@ function MyComponent() {
141
148
  />
142
149
  ```
143
150
 
144
- ### Controlled
151
+ ## Controlled Mode
145
152
 
146
- 외부에서 값을 제어하는 예제입니다.
153
+ Pass `value` and `onChange` to manage the selection state externally. This is the recommended approach when the component is part of a form or when the selected values need to be used by other parts of the UI.
147
154
 
148
155
  ```tsx
149
156
  <Stack spacing={2}>
@@ -155,9 +162,9 @@ function MyComponent() {
155
162
  </Stack>
156
163
  ```
157
164
 
158
- ### Sorting
165
+ ## Sorting Behavior
159
166
 
160
- 정렬 동작을 확인할 있는 예제입니다. 초기 렌더링 선택된 항목이 먼저 표시되고, 다음 알파벳 순으로 정렬됩니다.
167
+ The component automatically sorts options on initial render: selected items appear first, followed by unselected items in alphabetical order. When new options are added dynamically, they are inserted in the correct sorted position.
161
168
 
162
169
  ```tsx
163
170
  <Stack spacing={2}>
@@ -190,3 +197,122 @@ function MyComponent() {
190
197
  </Typography>
191
198
  </Stack>
192
199
  ```
200
+
201
+ ## Disabled State
202
+
203
+ The component supports both full and partial disabling.
204
+
205
+ - **Full disable**: Set `disabled` on the component to disable the search input, "Select all" toggle, and all checkboxes.
206
+ - **Partial disable**: Set `disabled: true` on individual option objects to disable specific items while keeping the rest interactive.
207
+ - **"Select all" behavior**: Disabled options are excluded from the "Select all" toggle. Their selected state is preserved regardless of bulk actions.
208
+
209
+ ```tsx
210
+ <Stack spacing={3}>
211
+ <FilterableCheckboxGroup label="Entirely Disabled" placeholder="Search..." helperText="All inputs are disabled" options={defaultOptions.slice(0, 5)} disabled />
212
+ <FilterableCheckboxGroup label="Partially Disabled Options" placeholder="Search..." helperText="Some options are disabled" options={disabledOptions} />
213
+ <Stack spacing={2}>
214
+ <FilterableCheckboxGroup label="Controlled + Partially Disabled" placeholder="Search..." helperText="Disabled options (Banana, Date) maintain their selected state" options={disabledOptions} value={controlledValue} onChange={setControlledValue} />
215
+ <Typography level="body-sm">
216
+ Selected: {controlledValue.length > 0 ? controlledValue.join(', ') : 'None'}
217
+ </Typography>
218
+ <Typography level="body-sm" sx={{
219
+ color: 'text.secondary'
220
+ }}>
221
+ Try "Select all" - it will only affect enabled options (Apple, Cherry, Elderberry)
222
+ </Typography>
223
+ </Stack>
224
+ </Stack>
225
+ ```
226
+
227
+ ## Common Use Cases
228
+
229
+ ### Filter Panel
230
+
231
+ Use FilterableCheckboxGroup in a sidebar or panel to let users filter a data table or list by multiple criteria.
232
+
233
+ ```tsx
234
+ import { FilterableCheckboxGroup } from '@ceed/cds';
235
+
236
+ function FilterPanel({ categories, onFilterChange }) {
237
+ const [selected, setSelected] = useState<string[]>([]);
238
+
239
+ const handleChange = (values: string[]) => {
240
+ setSelected(values);
241
+ onFilterChange(values);
242
+ };
243
+
244
+ return (
245
+ <FilterableCheckboxGroup
246
+ label="Categories"
247
+ placeholder="Search categories..."
248
+ options={categories}
249
+ value={selected}
250
+ onChange={handleChange}
251
+ maxHeight={200}
252
+ />
253
+ );
254
+ }
255
+ ```
256
+
257
+ ### User Role Assignment
258
+
259
+ Assign multiple roles to a user from a searchable list.
260
+
261
+ ```tsx
262
+ <FilterableCheckboxGroup
263
+ label="Assign Roles"
264
+ placeholder="Search roles..."
265
+ helperText="Select all roles that apply to this user"
266
+ options={[
267
+ { value: 'admin', label: 'Administrator' },
268
+ { value: 'editor', label: 'Editor' },
269
+ { value: 'viewer', label: 'Viewer' },
270
+ { value: 'moderator', label: 'Moderator' },
271
+ { value: 'billing', label: 'Billing Manager' },
272
+ ]}
273
+ required
274
+ value={selectedRoles}
275
+ onChange={setSelectedRoles}
276
+ />
277
+ ```
278
+
279
+ ### Region / Country Selection
280
+
281
+ When dealing with a large set of options like countries, the search filter and virtual scrolling work together for fast selection.
282
+
283
+ ```tsx
284
+ <FilterableCheckboxGroup
285
+ label="Service Regions"
286
+ placeholder="Search countries..."
287
+ options={allCountries.map((c) => ({ value: c.code, label: c.name }))}
288
+ value={selectedCountries}
289
+ onChange={setSelectedCountries}
290
+ />
291
+ ```
292
+
293
+ ## Best Practices
294
+
295
+ 1. **Provide a meaningful placeholder.** The search input placeholder should hint at the type of content being filtered (e.g., "Search countries..." instead of a generic "Search...").
296
+
297
+ ```tsx
298
+ // ✅ Specific and helpful
299
+ <FilterableCheckboxGroup placeholder="Search countries..." />
300
+
301
+ // ❌ Too generic
302
+ <FilterableCheckboxGroup placeholder="Type here..." />
303
+ ```
304
+
305
+ 2. **Use `maxHeight` to fit the layout.** In tight spaces like sidebars or modals, set a smaller `maxHeight` to prevent the component from dominating the layout.
306
+
307
+ 3. **Prefer controlled mode for form integration.** Always pass `value` and `onChange` when the selected values participate in form submission or affect other UI state.
308
+
309
+ 4. **Leverage individual `disabled` for permissions.** When some options should be visible but not selectable (e.g., due to user permissions), disable them individually rather than hiding them. This communicates availability without surprising users.
310
+
311
+ 5. **Keep option labels concise.** Long labels will be truncated with an ellipsis. If detailed descriptions are needed, consider using a tooltip or a separate detail view.
312
+
313
+ ## Accessibility
314
+
315
+ - The search input is labeled by the component's `label` prop, ensuring screen readers announce its purpose.
316
+ - Each checkbox option uses a native `<input type="checkbox">` with an associated `<label>`, enabling click-on-label and proper screen reader announcements.
317
+ - The "Select all" toggle clearly communicates its tri-state behavior (all, none, indeterminate) to assistive technologies.
318
+ - Keyboard navigation is fully supported: Tab to move between the search input and the checkbox list, Space to toggle individual checkboxes, and standard arrow keys to navigate within the list.
@@ -0,0 +1,361 @@
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.