packages/codemod/docs/FORM_CONTROL_MIGRATION.md
This document outlines the migration from Chakra UI v2 FormControl components to v3 Field and Fieldset.
| v2 Component | v3 Component |
|---|---|
FormControl | Field.Root |
FormLabel | Field.Label |
FormHelperText | Field.HelperText |
FormErrorMessage | Field.ErrorText |
| v2 Component | v3 Component |
|---|---|
FormControl as='fieldset' | Fieldset.Root |
FormLabel as='legend' | Fieldset.Legend |
FormHelperText | Fieldset.HelperText |
FormErrorMessage | Fieldset.ErrorText |
| Component | Description |
|---|---|
Field.ErrorIcon | Icon displayed in error messages |
Field.RequiredIndicator | Indicator for required fields (e.g., *) |
Fieldset.Content | Content wrapper for fieldset body |
| v2 Prop | v3 Prop | Notes |
|---|---|---|
isInvalid | invalid | Boolean prop |
isRequired | required | Boolean prop |
isDisabled | disabled | Boolean prop |
isReadOnly | readOnly | Boolean prop |
v2:
import {
FormControl,
FormErrorMessage,
FormHelperText,
FormLabel,
} from "@chakra-ui/react"
v3 (Standard Form):
import { Field } from "@chakra-ui/react"
v3 (Fieldset):
import { Fieldset } from "@chakra-ui/react"
v2:
import { FormControl, FormHelperText, FormLabel, Input } from "@chakra-ui/react"
;<FormControl>
<FormLabel>Email address</FormLabel>
<Input type="email" />
<FormHelperText>We'll never share your email.</FormHelperText>
</FormControl>
v3:
import { Field, Input } from "@chakra-ui/react"
;<Field.Root>
<Field.Label>Email address</Field.Label>
<Input type="email" />
<Field.HelperText>We'll never share your email.</Field.HelperText>
</Field.Root>
v2:
import {
FormControl,
FormErrorMessage,
FormHelperText,
FormLabel,
Input,
} from "@chakra-ui/react"
function Example() {
const [input, setInput] = useState("")
const handleInputChange = (e) => setInput(e.target.value)
const isError = input === ""
return (
<FormControl isInvalid={isError}>
<FormLabel>Email</FormLabel>
<Input type="email" value={input} onChange={handleInputChange} />
{!isError ? (
<FormHelperText>
Enter the email you'd like to receive the newsletter on.
</FormHelperText>
) : (
<FormErrorMessage>Email is required.</FormErrorMessage>
)}
</FormControl>
)
}
v3:
import { Field, Input } from "@chakra-ui/react"
function Example() {
const [input, setInput] = useState("")
const handleInputChange = (e) => setInput(e.target.value)
const isError = input === ""
return (
<Field.Root invalid={isError}>
<Field.Label>Email</Field.Label>
<Input type="email" value={input} onChange={handleInputChange} />
{!isError ? (
<Field.HelperText>
Enter the email you'd like to receive the newsletter on.
</Field.HelperText>
) : (
<Field.ErrorText>Email is required.</Field.ErrorText>
)}
</Field.Root>
)
}
v2:
import { FormControl, FormLabel, Input } from "@chakra-ui/react"
;<FormControl isRequired>
<FormLabel>First name</FormLabel>
<Input placeholder="First name" />
</FormControl>
v3:
import { Field, Input } from "@chakra-ui/react"
;<Field.Root required>
<Field.Label>First name</Field.Label>
<Input placeholder="First name" />
</Field.Root>
v2:
import { FormControl, FormLabel, Input } from "@chakra-ui/react"
;<FormControl isDisabled>
<FormLabel>Email</FormLabel>
<Input type="email" />
</FormControl>
v3:
import { Field, Input } from "@chakra-ui/react"
;<Field.Root disabled>
<Field.Label>Email</Field.Label>
<Input type="email" />
</Field.Root>
v2:
import {
FormControl,
FormHelperText,
FormLabel,
HStack,
Radio,
RadioGroup,
} from "@chakra-ui/react"
;<FormControl as="fieldset">
<FormLabel as="legend">Favorite Naruto Character</FormLabel>
<RadioGroup defaultValue="Itachi">
<HStack spacing="24px">
<Radio value="Sasuke">Sasuke</Radio>
<Radio value="Nagato">Nagato</Radio>
<Radio value="Itachi">Itachi</Radio>
</HStack>
</RadioGroup>
<FormHelperText>Select only if you're a fan.</FormHelperText>
</FormControl>
v3:
import { Fieldset, HStack, Radio, RadioGroup } from "@chakra-ui/react"
;<Fieldset.Root>
<Fieldset.Legend>Favorite Naruto Character</Fieldset.Legend>
<RadioGroup defaultValue="Itachi">
<HStack spacing="24px">
<Radio value="Sasuke">Sasuke</Radio>
<Radio value="Nagato">Nagato</Radio>
<Radio value="Itachi">Itachi</Radio>
</HStack>
</RadioGroup>
<Fieldset.HelperText>Select only if you're a fan.</Fieldset.HelperText>
</Fieldset.Root>
v2:
import { FormControl, FormLabel, Input } from "@chakra-ui/react"
;<FormControl isReadOnly>
<FormLabel>Email</FormLabel>
<Input type="email" value="[email protected]" />
</FormControl>
v3:
import { Field, Input } from "@chakra-ui/react"
;<Field.Root readOnly>
<Field.Label>Email</Field.Label>
<Input type="email" value="[email protected]" />
</Field.Root>
To automatically migrate your FormControl components, run:
npx @chakra-ui/codemod transform form-control <path>
--dry - Do a dry-run without making changes--print - Print the changed output for comparison# Transform all files in src directory
npx @chakra-ui/codemod transform form-control ./src
# Dry run to preview changes
npx @chakra-ui/codemod transform form-control ./src --dry
# Print changes for comparison
npx @chakra-ui/codemod transform form-control ./src --print
If you prefer to migrate manually:
Field (or Fieldset for fieldset forms)FormControl → Field.Root (or Fieldset.Root)FormLabel → Field.Label (or Fieldset.Legend for fieldsets)FormHelperText → Field.HelperText (or Fieldset.HelperText)FormErrorMessage → Field.ErrorText (or Fieldset.ErrorText)isInvalid → invalidisRequired → requiredisDisabled → disabledisReadOnly → readOnlyFormControl as='fieldset' with Fieldset.RootFormLabel as='legend' with Fieldset.LegendFormControl as='fieldset' replaced with dedicated Fieldset componentFormErrorMessage renamed to Field.ErrorText / Fieldset.ErrorTextis* to lowercase (e.g., isInvalid →
invalid)The v3 Field and Fieldset components provide:
is prefixErrorIcon, RequiredIndicator for enhanced UXField for standard form controls (input, textarea, select)Fieldset for grouped controls (radio groups, checkbox groups)as='fieldset' pattern is automatically detected and transformed to
Fieldset.Root