packages/codemod/docs/PIN_INPUT_MIGRATION.md
This document outlines the migration from Chakra UI v2 PinInput component to v3.
import { PinInput, PinInputField } from "@chakra-ui/react"
;<PinInput>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
import { PinInput } from "@chakra-ui/react"
;<PinInput.Root>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
New Required Elements:
<PinInput.HiddenInput /> - Must be first child of Root<PinInput.Control> - Must wrap all Input elementsInput Index Props:
<PinInput.Input> requires an index prop (0-indexed)| v2 Prop | v3 Prop | Notes |
|---|---|---|
value | value | Now accepts string[] instead of string |
defaultValue | defaultValue | Now accepts string[] instead of string |
| v2 Prop | v3 Prop | Notes |
|---|---|---|
isDisabled | disabled | Standard HTML attribute |
isInvalid | invalid | Simplified prop name |
| v2 Prop | v3 Prop | Notes |
|---|---|---|
onChange | onValueChange | Receives object: { value, valueAsString } |
onComplete | onValueComplete | Receives object: { value, valueAsString } |
| v2 Prop | v3 Equivalent | Notes |
|---|---|---|
manageFocus | (removed) | No longer supported in v3 |
| v2 Prop | v3 Prop/Behavior | Notes |
|---|---|---|
focusBorderColor | CSS variable: --focus-color | Applied to Input, moved to css prop |
errorBorderColor | CSS variable: --error-color | Applied to Input, moved to css prop |
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput type="alphanumeric">
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root type="alphanumeric">
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput otp>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root otp>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput type="alphanumeric" mask>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root type="alphanumeric" mask>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput defaultValue="234">
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root defaultValue={["2", "3", "4"]}>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput defaultValue="23">
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root defaultValue={["2", "3"]}>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput placeholder="🥳">
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root placeholder="🥳">
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput isDisabled>
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root disabled>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
;<HStack>
<PinInput isInvalid>
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
v3:
import { HStack, PinInput } from "@chakra-ui/react"
;<HStack>
<PinInput.Root invalid>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
</PinInput.Control>
</PinInput.Root>
</HStack>
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
import { useState } from "react"
function App() {
const [value, setValue] = useState("1234")
return (
<HStack>
<PinInput value={value} onChange={setValue}>
<PinInputField />
<PinInputField />
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
)
}
v3:
import { HStack, PinInput } from "@chakra-ui/react"
import { useState } from "react"
function App() {
const [value, setValue] = useState(["1", "2", "3", "4"])
return (
<HStack>
<PinInput.Root value={value} onValueChange={(e) => setValue(e.value)}>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
<PinInput.Input index={2} />
<PinInput.Input index={3} />
</PinInput.Control>
</PinInput.Root>
</HStack>
)
}
v2:
import { HStack, PinInput, PinInputField } from "@chakra-ui/react"
function App() {
const handleChange = (value) => console.log("change", value)
const handleComplete = (value) => console.log("complete", value)
return (
<HStack>
<PinInput onChange={handleChange} onComplete={handleComplete}>
<PinInputField />
<PinInputField />
</PinInput>
</HStack>
)
}
v3:
import { HStack, PinInput } from "@chakra-ui/react"
function App() {
const handleChange = (e) => console.log("change", e.value)
const handleComplete = (e) => console.log("complete", e.value)
return (
<HStack>
<PinInput.Root
onValueChange={handleChange}
onValueComplete={handleComplete}
>
<PinInput.HiddenInput />
<PinInput.Control>
<PinInput.Input index={0} />
<PinInput.Input index={1} />
</PinInput.Control>
</PinInput.Root>
</HStack>
)
}
To automatically migrate your PinInput components, run:
npx @chakra-ui/codemod transform pin-input <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 pin-input ./src
# Dry run to preview changes
npx @chakra-ui/codemod transform pin-input ./src --dry
# Print changes for comparison
npx @chakra-ui/codemod transform pin-input ./src --print
If you prefer to migrate manually:
Update Component Names:
<PinInput> → <PinInput.Root><PinInputField> → <PinInput.Input>Add Required Elements:
<PinInput.HiddenInput /> as first child of Root<PinInput.Control>Add Index Props:
index={0}, index={1}, etc. to each Input elementUpdate Value Props:
value='1234' → value={['1', '2', '3', '4']}defaultValue='234' → defaultValue={['2', '3', '4']}value={pin} → value={pin.split('')}Update Props:
isDisabled → disabledisInvalid → invalidonChange → onValueChange (receives { value, valueAsString })onComplete → onValueComplete (receives { value, valueAsString })manageFocusUpdate Styling Props:
focusBorderColor/errorBorderColor from Input to CSS variablesPinInput → PinInput.Root (namespace component)PinInputField → PinInput.Input (requires index prop)value now accepts string[] instead of stringdefaultValue now accepts string[] instead of string'234' → ['2', '3', '4']{pin} → {pin.split('')}isDisabled → disabledisInvalid → invalidonChange → onValueChangeonComplete → onValueCompletemanageFocus - no longer supportedonChange(value) → onValueChange({ value, valueAsString })onComplete(value) → onValueComplete({ value, valueAsString })focusBorderColor and errorBorderColor moved to CSS variablescss prop on Input: css={{ '--focus-color': 'red.200' }}The codemod will:
<PinInput> to <PinInput.Root> with proper structure<PinInput.HiddenInput /> as first child<PinInput.Control><PinInputField> to <PinInput.Input> with auto-indexed props'234' → ['2', '3', '4']).split('') methodmanageFocus propfocusBorderColor/errorBorderColor to CSS variablesThe v3 PinInput component provides:
value and defaultValue now accept string[] instead of
string:
value='1234' →
value={['1', '2', '3', '4']}value={pin} → value={pin.split('')}onValueChange and onValueComplete handlers receive an object
{ value, valueAsString }
value is the array of charactersvalueAsString is the joined string value<PinInput.Input> requires an index prop (0-indexed)<PinInput.HiddenInput /> is required for proper form integration and
accessibility<PinInput.Control> is required to wrap all Input elements