FormControl
Groups a label, control, and helper text into one accessible field and shares their state through React context. Works standalone or inside a Form.
Architecture: FormControl renders a View and provides FormControlContext. It auto-generates an id via useId() (override with the id prop) and derives helperId = ${id}-helper. Descendant controls read field state through useFormControl; FormLabel reads the id for htmlFor; FormHelperText registers its presence so the control points aria-describedby at it only while it is rendered - no dangling reference, no manual wiring.
Override chain
Field state resolves explicit prop โ FormControl โ Form โ default. A prop set on the control always wins; otherwise FormControl fills it; otherwise the surrounding Form; otherwise the control's own default.
error and required are field-only - they are never read from Form.
Examples
Default
We'll never share it.
const args: Partial<FormControlProps> = { required: true, fullWidth: true }
<FormControl {...args}>
<Flex flexDirection="column" gap={1} alignItems="flex-start">
<FormLabel>Email</FormLabel>
<TextInput variant="outlined" placeholder="[email protected]" />
<FormHelperText>We'll never share it.</FormHelperText>
</Flex>
</FormControl>
Error State
Enter a valid e-mail address.
const args: Partial<FormControlProps> = { error: true, required: true, fullWidth: true }
<FormControl {...args}>
<Flex flexDirection="column" gap={1} alignItems="flex-start">
<FormLabel>Email</FormLabel>
<TextInput variant="outlined" value="not-an-email" onChange={() => {}} />
<FormHelperText>Enter a valid e-mail address.</FormHelperText>
</Flex>
</FormControl>