Back to Chakra Ui

Drawer Migration Guide (v2 → v3)

packages/codemod/docs/DRAWER_MIGRATION.md

0.3.0-beta9.7 KB
Original Source

Drawer Migration Guide (v2 → v3)

This guide covers the migration of Chakra UI v2 Drawer components to the v3 compound component API.

Overview

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.

Component Mapping

v2 Componentv3 Component
<Drawer><Drawer.Root>
<DrawerOverlay><Drawer.Backdrop>
<DrawerContent><Drawer.Positioner> + <Drawer.Content>
<DrawerHeader><Drawer.Header>
<DrawerBody><Drawer.Body>
<DrawerFooter><Drawer.Footer>
<DrawerCloseButton><Drawer.CloseTrigger>

Portal Wrapper

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:

tsx
<Drawer isOpen={isOpen} onClose={onClose}>
  <DrawerOverlay />
  <DrawerContent></DrawerContent>
</Drawer>

v3:

tsx
<Drawer.Root
  open={isOpen}
  onOpenChange={(e) => {
    if (!e.open) onClose()
  }}
>
  <Portal>
    <Drawer.Backdrop />
    <Drawer.Positioner>
      <Drawer.Content></Drawer.Content>
    </Drawer.Positioner>
  </Portal>
</Drawer.Root>

Prop Transformations

Drawer.Root Props

v2 Propv3 PropNotes
isOpenopenDirect rename
onCloseonOpenChangeNow 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
sizesizeValues 2xl-6xl map to xl
isCenteredplacement="center"For centered drawers
closeOnEsccloseOnEscapeDirect rename
closeOnOverlayClickcloseOnInteractOutsideRenamed for clarity
blockScrollOnMountpreventScrollRenamed for clarity
onCloseCompleteonExitCompleteDirect rename
onEsconEscapeKeyDownDirect rename
onOverlayClickonInteractOutsideRenamed for clarity
finalFocusReffinalFocusElNow uses function: () => ref.current
initialFocusRefinitialFocusElNow uses function: () => ref.current
motionPresetmotionPresetUnchanged
scrollBehaviorscrollBehaviorUnchanged
trapFocustrapFocusUnchanged

Removed Props

These props have been removed in v3:

  • allowPinchZoom
  • autoFocus
  • lockFocusAcrossFrames
  • preserveScrollBarGap
  • returnFocusOnClose
  • useInert
  • portalProps (use <Portal> component directly)

Placement Remapping

The placement prop has been updated for better RTL support:

tsx
// v2 → v3
placement="left"   → placement="start"
placement="right"  → placement="end"
placement="top"    → placement="top"     // unchanged
placement="bottom" → placement="bottom"  // unchanged

isFullHeight Transformation

In v2, isFullHeight was a prop on Drawer. In v3, it's applied as height="100%" on Drawer.Content:

v2:

tsx
<Drawer isOpen={isOpen} onClose={onClose} isFullHeight>
  <DrawerContent></DrawerContent>
</Drawer>

v3:

tsx
<Drawer.Root
  open={isOpen}
  onOpenChange={(e) => {
    if (!e.open) onClose()
  }}
>
  <Portal>
    <Drawer.Positioner>
      <Drawer.Content height="100%"></Drawer.Content>
    </Drawer.Positioner>
  </Portal>
</Drawer.Root>

onClose Handler Transformation

The onClose prop is transformed to onOpenChange with a conditional handler:

v2:

tsx
<Drawer isOpen={isOpen} onClose={handleClose}>

v3:

tsx
<Drawer.Root
  open={isOpen}
  onOpenChange={(e) => {
    if (!e.open) {
      handleClose()
    }
  }}
>

Size Remapping

In v3, drawer sizes are limited to: xs, sm, md, lg, xl, full. Larger v2 sizes are mapped to xl:

v2 Sizev3 Size
xsxs
smsm
mdmd
lglg
xlxl
2xlxl ⚠️
3xlxl ⚠️
4xlxl ⚠️
5xlxl ⚠️
6xlxl ⚠️
fullfull

Focus Management

Ref-based focus props now use functions returning the ref's .current value:

v2:

tsx
const cancelRef = useRef()
const submitRef = useRef()

<Drawer
  isOpen={isOpen}
  onClose={onClose}
  initialFocusRef={submitRef}
  finalFocusRef={cancelRef}
>

v3:

tsx
const cancelRef = useRef()
const submitRef = useRef()

<Drawer.Root
  open={isOpen}
  onOpenChange={(e) => { if (!e.open) onClose() }}
  initialFocusEl={() => submitRef.current}
  finalFocusEl={() => cancelRef.current}
>

Complete Example

Before (v2)

tsx
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>
    </>
  )
}

After (v3)

tsx
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>
    </>
  )
}

Import Changes

The codemod automatically consolidates Drawer component imports:

v2:

tsx
import {
  Drawer,
  DrawerBody,
  DrawerCloseButton,
  DrawerContent,
  DrawerFooter,
  DrawerHeader,
  DrawerOverlay,
} from "@chakra-ui/react"

v3:

tsx
import { Drawer, Portal } from "@chakra-ui/react"

Running the Codemod

bash
npx @chakra-ui/codemod@latest --transform drawer src/**/*.tsx

Manual Review Required

After running the codemod, review:

  1. Dynamic placement values: If placement is an expression, it's kept as-is. Verify RTL behavior.
  2. Custom Portal usage: If you had custom portal configurations via portalProps, migrate to using <Portal> component props.
  3. isFullHeight with existing height: If DrawerContent already had a height prop, the codemod adds height="100%" which may override it.
  4. Size expressions: If size is a variable/expression, the codemod keeps it as-is. Ensure your size values match v3's available sizes.

Additional Resources