packages/codemod/docs/DRAWER_MIGRATION.md
This guide covers the migration of Chakra UI v2 Drawer components to the v3 compound component API.
In v3, Drawer has been redesigned with a compound component pattern that provides more flexibility and clearer component relationships. The codemod automatically handles most transformations.
| v2 Component | v3 Component |
|---|---|
<Drawer> | <Drawer.Root> |
<DrawerOverlay> | <Drawer.Backdrop> |
<DrawerContent> | <Drawer.Positioner> + <Drawer.Content> |
<DrawerHeader> | <Drawer.Header> |
<DrawerBody> | <Drawer.Body> |
<DrawerFooter> | <Drawer.Footer> |
<DrawerCloseButton> | <Drawer.CloseTrigger> |
In v2, Drawer automatically rendered content in a portal. In v3, you need to
explicitly wrap the backdrop and positioner in a <Portal> component. The
codemod handles this automatically.
v2:
<Drawer isOpen={isOpen} onClose={onClose}>
<DrawerOverlay />
<DrawerContent></DrawerContent>
</Drawer>
v3:
<Drawer.Root
open={isOpen}
onOpenChange={(e) => {
if (!e.open) onClose()
}}
>
<Portal>
<Drawer.Backdrop />
<Drawer.Positioner>
<Drawer.Content></Drawer.Content>
</Drawer.Positioner>
</Portal>
</Drawer.Root>
| v2 Prop | v3 Prop | Notes |
|---|---|---|
isOpen | open | Direct rename |
onClose | onOpenChange | Now receives event object with open property |
placement="left" | placement="start" | Remapped for RTL support |
placement="right" | placement="end" | Remapped for RTL support |
placement="top" | placement="top" | Unchanged |
placement="bottom" | placement="bottom" | Unchanged |
isFullHeight | (removed) | Adds height="100%" to Drawer.Content |
size | size | Values 2xl-6xl map to xl |
isCentered | placement="center" | For centered drawers |
closeOnEsc | closeOnEscape | Direct rename |
closeOnOverlayClick | closeOnInteractOutside | Renamed for clarity |
blockScrollOnMount | preventScroll | Renamed for clarity |
onCloseComplete | onExitComplete | Direct rename |
onEsc | onEscapeKeyDown | Direct rename |
onOverlayClick | onInteractOutside | Renamed for clarity |
finalFocusRef | finalFocusEl | Now uses function: () => ref.current |
initialFocusRef | initialFocusEl | Now uses function: () => ref.current |
motionPreset | motionPreset | Unchanged |
scrollBehavior | scrollBehavior | Unchanged |
trapFocus | trapFocus | Unchanged |
These props have been removed in v3:
allowPinchZoomautoFocuslockFocusAcrossFramespreserveScrollBarGapreturnFocusOnCloseuseInertportalProps (use <Portal> component directly)The placement prop has been updated for better RTL support:
// v2 → v3
placement="left" → placement="start"
placement="right" → placement="end"
placement="top" → placement="top" // unchanged
placement="bottom" → placement="bottom" // unchanged
In v2, isFullHeight was a prop on Drawer. In v3, it's applied as
height="100%" on Drawer.Content:
v2:
<Drawer isOpen={isOpen} onClose={onClose} isFullHeight>
<DrawerContent></DrawerContent>
</Drawer>
v3:
<Drawer.Root
open={isOpen}
onOpenChange={(e) => {
if (!e.open) onClose()
}}
>
<Portal>
<Drawer.Positioner>
<Drawer.Content height="100%"></Drawer.Content>
</Drawer.Positioner>
</Portal>
</Drawer.Root>
The onClose prop is transformed to onOpenChange with a conditional handler:
v2:
<Drawer isOpen={isOpen} onClose={handleClose}>
v3:
<Drawer.Root
open={isOpen}
onOpenChange={(e) => {
if (!e.open) {
handleClose()
}
}}
>
In v3, drawer sizes are limited to: xs, sm, md, lg, xl, full. Larger
v2 sizes are mapped to xl:
| v2 Size | v3 Size |
|---|---|
xs | xs |
sm | sm |
md | md |
lg | lg |
xl | xl |
2xl | xl ⚠️ |
3xl | xl ⚠️ |
4xl | xl ⚠️ |
5xl | xl ⚠️ |
6xl | xl ⚠️ |
full | full |
Ref-based focus props now use functions returning the ref's .current value:
v2:
const cancelRef = useRef()
const submitRef = useRef()
<Drawer
isOpen={isOpen}
onClose={onClose}
initialFocusRef={submitRef}
finalFocusRef={cancelRef}
>
v3:
const cancelRef = useRef()
const submitRef = useRef()
<Drawer.Root
open={isOpen}
onOpenChange={(e) => { if (!e.open) onClose() }}
initialFocusEl={() => submitRef.current}
finalFocusEl={() => cancelRef.current}
>
import {
Button,
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
useDisclosure,
} from "@chakra-ui/react"
function DrawerExample() {
const { isOpen, onOpen, onClose } = useDisclosure()
const btnRef = useRef()
return (
<>
<Button ref={btnRef} colorScheme="teal" onClick={onOpen}>
Open
</Button>
<Drawer
isOpen={isOpen}
placement="right"
onClose={onClose}
finalFocusRef={btnRef}
isFullHeight
size="md"
closeOnEsc={false}
>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Create your account</DrawerHeader>
<DrawerBody>
<input placeholder="Type here..." />
</DrawerBody>
<DrawerFooter>
<Button variant="outline" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue">Save</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
</>
)
}
import { Button, Drawer, Portal, useDisclosure } from "@chakra-ui/react"
function DrawerExample() {
const { isOpen, onOpen, onClose } = useDisclosure()
const btnRef = useRef()
return (
<>
<Button ref={btnRef} colorScheme="teal" onClick={onOpen}>
Open
</Button>
<Drawer.Root
open={isOpen}
placement="end"
finalFocusEl={() => btnRef.current}
size="md"
closeOnEscape={false}
onOpenChange={(e) => {
if (!e.open) {
onClose()
}
}}
>
<Portal>
<Drawer.Backdrop />
<Drawer.Positioner>
<Drawer.Content height="100%">
<Drawer.CloseTrigger />
<Drawer.Header>Create your account</Drawer.Header>
<Drawer.Body>
<input placeholder="Type here..." />
</Drawer.Body>
<Drawer.Footer>
<Button variant="outline" mr={3} onClick={onClose}>
Cancel
</Button>
<Button colorScheme="blue">Save</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Positioner>
</Portal>
</Drawer.Root>
</>
)
}
The codemod automatically consolidates Drawer component imports:
v2:
import {
Drawer,
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
} from "@chakra-ui/react"
v3:
import { Drawer, Portal } from "@chakra-ui/react"
npx @chakra-ui/codemod@latest --transform drawer src/**/*.tsx
After running the codemod, review:
portalProps, migrate to using <Portal> component props.DrawerContent already had a
height prop, the codemod adds height="100%" which may override it.size is a variable/expression, the codemod keeps
it as-is. Ensure your size values match v3's available sizes.