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

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>

FormControl API reference