packages/codemod/docs/EDITABLE_MIGRATION.md
This document outlines the migration from Chakra UI v2 Editable component to v3.
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Click to edit">
<EditablePreview />
<EditableInput />
</Editable>
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Click to edit">
<Editable.Preview />
<Editable.Input />
</Editable.Root>
| v2 Prop | v3 Prop | Notes |
|---|---|---|
isDisabled | disabled | Standard HTML attribute |
isPreviewFocusable | (special) | When false, adds tabIndex={undefined} to Preview |
selectAllOnFocus | selectOnFocus | Simplified prop name |
startWithEditView | defaultEdit | Renamed for clarity |
| v2 Prop | v3 Prop | Notes |
|---|---|---|
onChange | onValueChange | Receives object: { value } |
onSubmit | onValueCommit | Receives value string |
onCancel | onValueRevert | Called when edit is cancelled |
| v2 Prop | v3 Prop/Behavior | Notes |
|---|---|---|
finalFocusRef | finalFocusEl | Now a function: () => ref.current |
submitOnBlur | submitMode | When false, becomes submitMode='enter' |
| v2 Hook | v3 Hook | Notes |
|---|---|---|
useEditableControls | useEditableContext | Provides same context API |
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Take some chakra">
<EditablePreview />
<EditableInput />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Take some chakra">
<Editable.Preview />
<Editable.Input />
</Editable.Root>
v2:
import { Editable, EditablePreview, EditableTextarea } from "@chakra-ui/react"
;<Editable defaultValue="Take some chakra">
<EditablePreview />
<EditableTextarea />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Take some chakra">
<Editable.Preview />
<Editable.Textarea />
</Editable.Root>
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Disabled" isDisabled>
<EditablePreview />
<EditableInput />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Disabled" disabled>
<Editable.Preview />
<Editable.Input />
</Editable.Root>
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
function App() {
const handleChange = (value) => console.log("change", value)
const handleSubmit = (value) => console.log("submit", value)
const handleCancel = () => console.log("cancel")
return (
<Editable
defaultValue="Edit me"
onChange={handleChange}
onSubmit={handleSubmit}
onCancel={handleCancel}
>
<EditablePreview />
<EditableInput />
</Editable>
)
}
v3:
import { Editable } from "@chakra-ui/react"
function App() {
const handleChange = ({ value }) => console.log("change", value)
const handleSubmit = (value) => console.log("submit", value)
const handleCancel = () => console.log("cancel")
return (
<Editable.Root
defaultValue="Edit me"
onValueChange={handleChange}
onValueCommit={handleSubmit}
onValueRevert={handleCancel}
>
<Editable.Preview />
<Editable.Input />
</Editable.Root>
)
}
v2:
import {
Editable,
EditableInput,
EditablePreview,
Input,
} from "@chakra-ui/react"
import { useRef } from "react"
function App() {
const ref = useRef(null)
return (
<>
<Editable defaultValue="Final fantasy" finalFocusRef={ref}>
<EditablePreview />
<EditableInput />
</Editable>
<Input ref={ref} placeholder="Focus moves here" />
</>
)
}
v3:
import { Editable, Input } from "@chakra-ui/react"
import { useRef } from "react"
function App() {
const ref = useRef(null)
return (
<>
<Editable.Root
defaultValue="Final fantasy"
finalFocusEl={() => ref.current}
>
<Editable.Preview />
<Editable.Input />
</Editable.Root>
<Input ref={ref} placeholder="Focus moves here" />
</>
)
}
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Press Enter to submit" submitOnBlur={false}>
<EditablePreview />
<EditableInput />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Press Enter to submit" submitMode="enter">
<Editable.Preview />
<Editable.Input />
</Editable.Root>
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Already editing" startWithEditView>
<EditablePreview />
<EditableInput />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Already editing" defaultEdit>
<Editable.Preview />
<Editable.Input />
</Editable.Root>
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
;<Editable defaultValue="Click to edit" isPreviewFocusable={false}>
<EditablePreview />
<EditableInput />
</Editable>
v3:
import { Editable } from "@chakra-ui/react"
;<Editable.Root defaultValue="Click to edit">
<Editable.Preview tabIndex={undefined} />
<Editable.Input />
</Editable.Root>
v2:
import { CheckIcon, CloseIcon, EditIcon } from "@chakra-ui/icons"
import {
ButtonGroup,
Editable,
EditableInput,
EditablePreview,
Flex,
IconButton,
useEditableControls,
} from "@chakra-ui/react"
function EditableControls() {
const {
isEditing,
getSubmitButtonProps,
getCancelButtonProps,
getEditButtonProps,
} = useEditableControls()
return isEditing ? (
<ButtonGroup size="sm">
<IconButton icon={<CheckIcon />} {...getSubmitButtonProps()} />
<IconButton icon={<CloseIcon />} {...getCancelButtonProps()} />
</ButtonGroup>
) : (
<Flex>
<IconButton size="sm" icon={<EditIcon />} {...getEditButtonProps()} />
</Flex>
)
}
function App() {
return (
<Editable defaultValue="Rasengan ⚡️">
<EditablePreview />
<EditableInput />
<EditableControls />
</Editable>
)
}
v3:
import { Editable, IconButton } from "@chakra-ui/react"
import { LuCheck, LuPencilLine, LuX } from "react-icons/lu"
function App() {
return (
<Editable.Root defaultValue="Rasengan ⚡️">
<Editable.Preview />
<Editable.Input />
<Editable.Control>
<Editable.EditTrigger asChild>
<IconButton variant="ghost" size="xs">
<LuPencilLine />
</IconButton>
</Editable.EditTrigger>
<Editable.CancelTrigger asChild>
<IconButton variant="outline" size="xs">
<LuX />
</IconButton>
</Editable.CancelTrigger>
<Editable.SubmitTrigger asChild>
<IconButton variant="outline" size="xs">
<LuCheck />
</IconButton>
</Editable.SubmitTrigger>
</Editable.Control>
</Editable.Root>
)
}
v2:
import { useEditableControls } from "@chakra-ui/react"
function CustomComponent() {
const { isEditing, getEditButtonProps } = useEditableControls()
return <button {...getEditButtonProps()}>Edit</button>
}
v3:
import { useEditableContext } from "@chakra-ui/react"
function CustomComponent() {
const editable = useEditableContext()
return <button onClick={() => editable.edit()}>Edit</button>
}
v2:
import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"
function App() {
return (
<Editable
defaultValue="Rasengan ⚡️"
fontSize="2xl"
isPreviewFocusable={false}
submitOnBlur={false}
selectAllOnFocus
onSubmit={(value) => console.log("submitted:", value)}
onChange={(value) => console.log("changed:", value)}
>
<EditablePreview />
<EditableInput />
</Editable>
)
}
v3:
import { Editable } from "@chakra-ui/react"
function App() {
return (
<Editable.Root
defaultValue="Rasengan ⚡️"
fontSize="2xl"
submitMode="enter"
selectOnFocus
onValueCommit={(value) => console.log("submitted:", value)}
onValueChange={({ value }) => console.log("changed:", value)}
>
<Editable.Preview tabIndex={undefined} />
<Editable.Input />
</Editable.Root>
)
}
To automatically migrate your Editable components, run:
npx @chakra-ui/codemod transform editable <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 editable ./src
# Dry run to preview changes
npx @chakra-ui/codemod transform editable ./src --dry
# Print changes for comparison
npx @chakra-ui/codemod transform editable ./src --print
If you prefer to migrate manually:
Update Component Names:
<Editable> → <Editable.Root><EditablePreview> → <Editable.Preview><EditableInput> → <Editable.Input><EditableTextarea> → <Editable.Textarea>Update Props:
isDisabled → disabledonCancel → onValueRevertonChange → onValueChange (receives { value })onSubmit → onValueCommitselectAllOnFocus → selectOnFocusstartWithEditView → defaultEditsubmitOnBlur={false} → submitMode='enter'finalFocusRef={ref} → finalFocusEl={() => ref.current}Handle Special Props:
isPreviewFocusable={false}, add tabIndex={undefined} to
<Editable.Preview>isPreviewFocusable from RootUpdate Hooks:
useEditableControls with useEditableContextUpdate Custom Controls:
<Editable.Control>, <Editable.EditTrigger>,
<Editable.SubmitTrigger>, <Editable.CancelTrigger>Editable → Editable.Root (namespace component)EditablePreview → Editable.PreviewEditableInput → Editable.InputEditableTextarea → Editable.TextareaisDisabled → disabledonCancel → onValueRevertonChange → onValueChangeonSubmit → onValueCommitselectAllOnFocus → selectOnFocusstartWithEditView → defaultEditfinalFocusRef → finalFocusEl (now a function)submitOnBlur={false} → submitMode='enter'isPreviewFocusable={false} → Add tabIndex={undefined} to PreviewuseEditableControls → useEditableContextonChange(value) → onValueChange({ value })onSubmit(value) → onValueCommit(value) (unchanged)onCancel() → onValueRevert() (renamed only)The codemod will:
isDisabled → disabledonChange, onSubmit, onCancelselectAllOnFocus → selectOnFocusstartWithEditView → defaultEditsubmitOnBlur={false} → submitMode='enter'finalFocusRef → finalFocusEl with function wrapperisPreviewFocusable={false} by adding tabIndex={undefined} to
PreviewuseEditableControls → useEditableContext (imports and calls)The v3 Editable component provides:
useEditableContext provides full control over
edit stateonValue* patternonValueChange handler in v3 receives an object { value }, not just the
value stringfinalFocusEl prop must be a function that returns the element, not a ref
objectsubmitOnBlur is omitted or true, no submitMode prop is needed
(default behavior)isPreviewFocusable={false} transformation adds tabIndex={undefined}
directly to PreviewuseEditableContext) provides methods like edit(),
cancel(), submit() instead of prop getters