v3로 마이그레이션

v3로 마이그레이션 (Migration to v3)

Chakra UI v2.x에서 v3.x로 마이그레이션하는 방법을 안내하는 가이드예요. codemod로 자동화된 마이그레이션부터 수동 단계, 제거된 기능, prop 변화, 컴포넌트 변화까지 상세히 다룹니다.

출처: 문서

본문

:::warning

대규모 언어 모델이 Chakra UI v3 문서를 사용할 수 있도록 LLMs.txt 파일을 사용하는 것을 권장해요.

:::

Codemod (권장)

codemod는 Chakra UI v2에서 v3로의 마이그레이션을 자동화해요. 컴포넌트 이름 변경, prop 변경, import 업데이트, 컴파운드 컴포넌트 재구성을 처리합니다. 수동으로 마이그레이션하기 전에 여기서 시작하세요.

npx @chakra-ui/codemod upgrade

--dry를 사용하면 파일을 수정하지 않고 변경 사항을 미리 볼 수 있어요.

수동 단계 (Manual Steps)

최소 Node 버전은 Node.20.x입니다.

패키지 업데이트 (Update Packages)

더 이상 사용하지 않는 패키지 @emotion/styled와 framer-motion을 제거하세요. 이 패키지들은 Chakra UI에서 더 이상 필요하지 않아요.

npm uninstall @emotion/styled framer-motion

@chakra-ui/react와 @emotion/react의 업데이트된 버전을 설치하세요.

npm install @chakra-ui/react@latest @emotion/react@latest

다음으로, CLI 스니펫을 사용해 컴포넌트 스니펫을 설치하세요. 스니펫은 시간을 절약하고 여러분이 제어할 수 있게 해 주는 Chakra 컴포넌트의 사전 빌드 조합을 제공해요.

npx @chakra-ui/cli snippet add

커스텀 테마 리팩터링 (Refactor Custom Theme)

커스텀 테마를 전용 theme.js 또는 theme.ts 파일로 옮기세요. 테마를 구성하려면 createSystem과 defaultConfig를 사용해요.

Before

import { extendTheme } from "@chakra-ui/react"

export const theme = extendTheme({
  fonts: {
    heading: `'Figtree', sans-serif`,
    body: `'Figtree', sans-serif`,
  },
})

After

import { createSystem, defaultConfig } from "@chakra-ui/react"

export const system = createSystem(defaultConfig, {
  theme: {
    tokens: {
      fonts: {
        heading: { value: `'Figtree', sans-serif` },
        body: { value: `'Figtree', sans-serif` },
      },
    },
  },
})

모든 토큰 값은 value 키를 가진 객체로 감싸야 해요. 토큰에 대해 더 알아보려면 여기를 참고하세요.

ChakraProvider 업데이트 (Update ChakraProvider)

ChakraProvider import를 @chakra-ui/react에서 스니펫의 것으로 업데이트하세요. 다음으로 theme prop을 value로 이름을 바꿔 새 시스템 기반 테마 방식과 일치시키세요.

Before

import { ChakraProvider } from "@chakra-ui/react"

export const App = ({ Component }) => (
  <ChakraProvider theme={theme}>
    <Component />
  </ChakraProvider>
)

After

import { Provider } from "@/components/ui/provider"
import { defaultSystem } from "@chakra-ui/react"

export const App = ({ Component }) => (
  <Provider>
    <Component />
  </Provider>
)
import { ColorModeProvider } from "@/components/ui/color-mode"
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"

export function Provider(props) {
  return (
    <ChakraProvider value={defaultSystem}>
      <ColorModeProvider {...props} />
    </ChakraProvider>
  )
}

커스텀 테마가 있다면 defaultSystem을 커스텀 system으로 바꾸세요.

Provider 컴포넌트는 Chakra의 ChakraProvider와 next-themes의 ThemeProvider를 조합해요.

개선 사항 (Improvements)

  • 성능: 재조정(reconciliation) 성능이 4x, 리렌더링 성능이 1.6x 개선되었습니다.

  • 네임스페이스 import (Namespaced imports): 더 간결한 import를 위해 점 표기법으로 컴포넌트를 import하세요.

    import { Accordion } from "@chakra-ui/react"
    
    const Demo = () => {
      return (
        <Accordion.Root>
          <Accordion.Item>
            <Accordion.ItemTrigger />
            <Accordion.ItemContent />
          </Accordion.Item>
        </Accordion.Root>
      )
    }
    
  • TypeScript: 스타일 prop과 토큰에 대한 IntelliSense와 타입 추론이 개선되었습니다.

  • 다형성 (Polymorphism): as prop 타이핑을 완화하고 asChild prop 사용을 지향합니다. 이 패턴은 Radix Primitives와 Ark UI에서 영감을 받았어요.

제거된 기능 (Removed Features)

컬러 모드 (Color Mode)

  • ColorModeProvider와 useColorMode는 next-themes를 위해 제거되었습니다.
  • LightMode, DarkMode, ColorModeScript 컴포넌트가 제거되었습니다. 이제 테마를 강제하려면 className="light" 또는 className="dark"를 사용해야 해요.
  • useColorModeValue는 next-themes의 useTheme을 위해 제거되었습니다.

:::note

CLI를 통해 컬러 모드 스니펫을 제공해 next-themes를 사용해 컬러 모드를 빠르게 설정할 수 있게 도와드려요.

:::

Hooks

전용의 견고한 라이브러리(react-use, usehooks-ts)를 위해 hooks 패키지를 제거했어요.

v2에서 남은 유일한 hooks는 useBreakpoint, useBreakpointValue, useCallbackRef, useConst, useControllableProp, useControllableState, useDisclosure, useMediaQuery, usePrevious, useSafeLayoutEffect, useUpdateEffect입니다.

useLatestRef는 이제 useLiveRef예요.

useBreakpoint는 더 이상 fallback 브레이크포인트를 문자열 인자로 받지 않아요. 대신 옵션으로 전달하세요.

// v2
useBreakpoint("md")

// v3
useBreakpoint({ fallback: "md" })

스타일 설정 (Style Config)

styleConfig와 multiStyleConfig 개념을 레시피와 슬롯 레시피를 위해 제거했어요. 이 패턴은 Panda CSS에서 영감을 받았습니다.

Next.js 패키지

더 나은 유연성을 위해 asChild prop을 사용하는 방식으로 @chakra-ui/next-js 패키지를 제거했어요.

Next.js 이미지 컴포넌트를 스타일링하려면 Box 컴포넌트에서 asChild prop을 사용하세요.

<Box asChild>
  <NextImage />
</Box>

Next.js 링크 컴포넌트를 스타일링하려면 Link 컴포넌트에서 asChild prop을 사용하세요.

<Link isExternal asChild>
  <NextLink />
</Link>

테마 도구 (Theme Tools)

CSS color mix를 사용하는 방식으로 이 패키지를 제거했어요.

Before

우리는 JS로 색상을 해석한 다음 투명도를 적용했어요.

defineStyle({
  bg: transparentize("blue.200", 0.16)(theme),
  // -> rgba(0, 0, 255, 0.16)
})

After

이제 CSS color-mix를 사용합니다.

defineStyle({
  bg: "blue.200/16",
  // -> color-mix(in srgb, var(--chakra-colors-blue-200), transparent 16%)
})

forwardRef

as prop 단순화로 인해 더 이상 커스텀 forwardRef를 제공하지 않아요. React의 forwardRef를 직접 사용하는 것을 선호합니다.

Before:

import { Button as ChakraButton, forwardRef } from "@chakra-ui/react"

const Button = forwardRef<ButtonProps, "button">(function Button(props, ref) {
  return <ChakraButton ref={ref} {...props} />
})

After:

import { Button as ChakraButton } from "@chakra-ui/react"
import { forwardRef } from "react"

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  function Button(props, ref) {
    return <ChakraButton ref={ref} {...props} />
  },
)

아이콘 (Icons)

@chakra-ui/icons 패키지를 제거했어요. react-icons(Lucide 아이콘 권장) 또는 lucide-react를 사용하세요. npm install react-icons로 설치합니다.

  • props 없는 아이콘 → react-icon을 직접 사용
  • Chakra 스타일 props가 있는 아이콘 → @chakra-ui/react의 Icon으로 감싸기

Before:

import { AddIcon, CheckIcon } from "@chakra-ui/icons"

<AddIcon />
<CheckIcon boxSize={6} color="green.500" />

After:

import { Icon } from "@chakra-ui/react"
import { LuCheck, LuPlus } from "react-icons/lu"

<LuPlus />
<Icon as={LuCheck} boxSize={6} color="green.500" />

일반적인 아이콘 매핑: AddIcon → LuPlus, CloseIcon → LuX, CheckIcon → LuCheck, EditIcon → LuPencil, DeleteIcon → LuTrash2, SearchIcon → LuSearch, ChevronDownIcon → LuChevronDown, ArrowForwardIcon → LuArrowRight, HamburgerIcon → LuMenu, WarningIcon → LuAlertTriangle, InfoIcon → LuInfo, ExternalLinkIcon → LuExternalLink, StarIcon → LuStar

커스텀 SVG 아이콘 (Custom SVG Icons)

v2에서 <Icon>은 <svg> 래퍼를 렌더링했고 SVG 자식(예: <path>)을 직접 전달했어요. v3에서 커스텀 SVG를 사용할 때는 asChild prop을 사용해 Icon이 자체 스타일을 <svg> 요소에 병합하도록 하세요.

Before:

import { Icon } from "@chakra-ui/react"

;<Icon viewBox="0 0 24 24" color="red.500" boxSize={6}>
  <path d="M12 2L2 22h20L12 2z" fill="currentColor" />
</Icon>

After:

import { Icon } from "@chakra-ui/react"

;<Icon color="red.500" size="md" asChild>
  <svg viewBox="0 0 24 24">
    <path d="M12 2L2 22h20L12 2z" fill="currentColor" />
  </svg>
</Icon>

또는 createIcon을 사용해 재사용 가능한 커스텀 아이콘을 정의할 수 있어요.

import { createIcon } from "@chakra-ui/react"

const TriangleIcon = createIcon({
  displayName: "TriangleIcon",
  viewBox: "0 0 24 24",
  path: <path d="M12 2L2 22h20L12 2z" fill="currentColor" />,
})

// Usage
<TriangleIcon size="lg" color="red.500" />

Storybook 애드온 (Storybook Addon)

@storybook/addon-themes와 withThemeByClassName 헬퍼를 사용하는 방식으로 storybook 애드온을 제거했어요.

import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
import { withThemeByClassName } from "@storybook/addon-themes"
import type { Preview, ReactRenderer } from "@storybook/react"

const preview: Preview = {
  decorators: [
    withThemeByClassName<ReactRenderer>({
      defaultTheme: "light",
      themes: {
        light: "",
        dark: "dark",
      },
    }),
    (Story) => (
      <ChakraProvider value={defaultSystem}>
        <Story />
      </ChakraProvider>
    ),
  ],
}

export default preview

제거된 컴포넌트 (Removed Components)

  • StackItem: 더 이상 필요하지 않아요. Box를 대신 사용하세요.
  • FocusLock: 더 이상 포커스 잠금 컴포넌트를 제공하지 않아요. react-focus-lock을 직접 설치하고 사용하세요.
  • AlertDialog
    • Dialog 컴포넌트로 대체하고 role=alertdialog를 설정하세요.
    • leastDestructiveRef prop을 Dialog.Root 컴포넌트의 initialFocusEl로 설정하세요.

CircularProgress

  • ProgressCircle로 이름이 바뀌었고 컴파운드 컴포넌트를 사용합니다.
  • isIndeterminate는 value={null}이 됩니다.
  • thickness prop은 --thickness CSS 변수가 됩니다.
  • color prop은 ProgressCircle.Range의 stroke prop이 됩니다.

Before:

<CircularProgress
  value={75}
  thickness="4px"
  color="blue.500"
  isIndeterminate={false}
/>

After:

<ProgressCircle.Root value={75}>
  <ProgressCircle.Circle css={{ "--thickness": "4px" }}>
    <ProgressCircle.Track />
    <ProgressCircle.Range stroke="blue.500" />
  </ProgressCircle.Circle>
</ProgressCircle.Root>

불확정(indeterminate) 진행바의 경우:

<ProgressCircle.Root value={null}>
  <ProgressCircle.Circle>
    <ProgressCircle.Track />
    <ProgressCircle.Range />
  </ProgressCircle.Circle>
</ProgressCircle.Root>

StackDivider

  • 더 이상 별도 컴포넌트로 제공되지 않아요.
  • 스택 항목 사이에 명시적 Separator 컴포넌트를 사용하세요.

Before:

<VStack divider={<StackDivider borderColor="gray.200" />} spacing={4}>
  <Box>Item 1</Box>
  <Box>Item 2</Box>
  <Box>Item 3</Box>
</VStack>

After:

import { Box, Separator, VStack } from "@chakra-ui/react"

;<VStack gap={4}>
  <Box>Item 1</Box>
  <Separator borderColor="gray.200" />
  <Box>Item 2</Box>
  <Separator borderColor="gray.200" />
  <Box>Item 3</Box>
</VStack>

Prop 변화 (Prop Changes)

불리언 prop (Boolean Props)

불리언 속성의 명명 규칙을 is<X>에서 <x>로 변경했어요.

  • isOpen -> open
  • defaultIsOpen -> defaultOpen
  • isDisabled -> disabled
  • isInvalid -> invalid
  • isRequired -> required

ColorScheme prop

colorScheme prop이 colorPalette로 변경되었어요.

Before

  • colorScheme은 컴포넌트 테마에서만 사용할 수 있었어요.
  • colorScheme은 HTML 요소의 네이티브 colorScheme prop과 충돌합니다.
<Button colorScheme="blue">Click me</Button>

After

  • 이제 어디서든 colorPalette를 사용할 수 있어요.
<Button colorPalette="blue">Click me</Button>

어떤 컴포넌트에서든 다음과 같이 사용할 수 있어요.

<Box colorPalette="red">
  <Box bg="colorPalette.400">Some box</Box>
  <Text color="colorPalette.600">Some text</Text>
</Box>

커스텀 색상을 사용한다면 colorPalette가 동작하도록 다음 두 가지를 정의해야 해요.

  • tokens: 50-950 컬러 팔레트용
  • semanticTokens: solid, contrast, fg, muted, subtle, emphasized, focusRing 색상 키용
import { createSystem, defaultConfig } from "@chakra-ui/react"

export const system = createSystem(defaultConfig, {
  theme: {
    tokens: {
      colors: {
        brand: {
          50: { value: "#e6f2ff" },
          100: { value: "#e6f2ff" },
          200: { value: "#bfdeff" },
          300: { value: "#99caff" },
          // ...
          950: { value: "#001a33" },
        },
      },
    },
    semanticTokens: {
      colors: {
        brand: {
          solid: { value: "{colors.brand.500}" },
          contrast: { value: "{colors.brand.100}" },
          fg: { value: "{colors.brand.700}" },
          muted: { value: "{colors.brand.100}" },
          subtle: { value: "{colors.brand.200}" },
          emphasized: { value: "{colors.brand.300}" },
          focusRing: { value: "{colors.brand.500}" },
        },
      },
    },
  },
})

이에 대해 더 알아보려면 여기를 참고하세요.

그라데이션 prop (Gradient Props)

그라데이션 스타일 prop을 gradient, gradientFrom, gradientTo prop으로 단순화했어요. 이렇게 하면 그라데이션 문자열을 파싱하는 런타임 성능 비용이 줄어들고 타입 추론이 더 좋아집니다.

Before

<Box bgGradient="linear(to-r, red.200, pink.500)" />

After

<Box bgGradient="to-r" gradientFrom="red.200" gradientTo="pink.500" />

컬러 팔레트 (Color Palette)

  • 기본 컬러 팔레트는 이제 모든 컴포넌트에서 gray이지만 테마에서 구성할 수 있어요.

  • 기본 테마 컬러 팔레트 크기가 더 다양한 색상 변형을 허용하도록 11가지 음영으로 늘어났어요.

    Before

    const colors = {
      // ...
      gray: {
        50: "#F7FAFC",
        100: "#EDF2F7",
        200: "#E2E8F0",
        300: "#CBD5E0",
        400: "#A0AEC0",
        500: "#718096",
        600: "#4A5568",
        700: "#2D3748",
        800: "#1A202C",
        900: "#171923",
      },
    }
    

    After

    const colors = {
      // ...
      gray: {
        50: { value: "#fafafa" },
        100: { value: "#f4f4f5" },
        200: { value: "#e4e4e7" },
        300: { value: "#d4d4d8" },
        400: { value: "#a1a1aa" },
        500: { value: "#71717a" },
        600: { value: "#52525b" },
        700: { value: "#3f3f46" },
        800: { value: "#27272a" },
        900: { value: "#18181b" },
        950: { value: "#09090b" },
      },
    }
    

스타일 prop (Style Props)

일부 스타일 prop의 명명 규칙을 변경했어요.

  • noOfLines -> lineClamp
  • truncated -> truncate
  • _activeLink -> _currentPage
  • _activeStep -> _currentStep
  • _mediaDark -> _osDark
  • _mediaLight -> _osLight

예시:

// Before
<Text noOfLines={2}>
  Long text that will be clamped to 2 lines
</Text>

<Text truncated>
  This text will be truncated with ellipsis
</Text>

// After
<Text lineClamp={2}>
  Long text that will be clamped to 2 lines
</Text>

<Text truncate>
  This text will be truncated with ellipsis
</Text>

textStyle 또는 layerStyles를 위해 apply prop을 제거했어요.

중첩 스타일 (Nested Styles)

Chakra UI 컴포넌트에서 중첩 스타일을 작성하는 방식을 변경했어요.

Before

sx 또는 __css prop을 사용해 중첩 스타일을 작성하며, 중첩 스타일에 대한 자동완성이 없는 경우도 있었어요.

<Box
  sx={{
    svg: { color: "red.500" },
  }}
/>

After

css prop을 사용해 중첩 스타일을 작성하세요. 모든 중첩 선택자는 앰퍼샌드 & 접두사를 필수로 사용해야 해요.

<Box
  css={{
    "& svg": { color: "red.500" },
  }}
/>

이렇게 한 이유는 두 가지입니다.

  • 더 빠른 스타일 처리: 이전에는 스타일 키가 스타일 prop인지 선택자인지 확인해야 했는데 전체적으로 비용이 꽤 컸어요.
  • 더 나은 타입: 중첩 스타일 prop을 강하게 타입화하기가 더 쉬워졌습니다.

컴포넌트 변화 (Component Changes)

ChakraProvider

  • theme prop을 제거하고 대신 system prop을 전달해요. theme 대신 defaultSystem 모듈을 import하세요.

  • resetCss prop을 제거하고 대신 createSystem 함수에 preflight: false를 전달하세요.

Before

<ChakraProvider resetCss={false}>
  <Component />
</ChakraProvider>

After

const system = createSystem(defaultConfig, { preflight: false })

<Provider value={system}>
  <Component />
</Provider>
  • toast 옵션 구성 지원을 제거했어요. 대신 components/ui/toaster.tsx 파일의 createToaster 함수에 전달하세요.

Dialog로 이름이 바뀌었고 명시적 Dialog.Positioner와 Portal 래퍼가 있는 컴파운드 컴포넌트를 사용합니다.

컴포넌트 이름 변경:

  • Modal → Dialog.Root
  • ModalOverlay → Dialog.Backdrop
  • ModalContent → Dialog.Content(Dialog.Positioner로 감쌈)
  • ModalHeader → Dialog.Header
  • ModalBody → Dialog.Body
  • ModalFooter → Dialog.Footer
  • ModalCloseButton → Dialog.CloseTrigger

Prop 변화:

  • isOpen → open
  • onClose → onOpenChange({ open }을 받음)
  • isCentered → placement="center"
  • closeOnOverlayClick → closeOnInteractOutside
  • closeOnEsc → closeOnEscape
  • blockScrollOnMount → preventScroll
  • onOverlayClick → onInteractOutside
  • onEsc → onEscapeKeyDown
  • onCloseComplete → onExitComplete
  • initialFocusRef → initialFocusEl={() => ref.current}
  • finalFocusRef → finalFocusEl={() => ref.current}

크기 매핑: 2xl부터 6xl까지의 크기는 v3에서 xl에 매핑됩니다.

제거된 prop: allowPinchZoom, lockFocusAcrossFrames, preserveScrollBarGap, returnFocusOnClose, useInert, portalProps

Avatar

별도의 Avatar.Image와 Avatar.Fallback 부분이 있는 선언적 구성 패턴을 사용합니다.

컴포넌트 이름 변경:

  • Avatar → Avatar.Root
  • AvatarBadge → 제거됨(Float + Circle 사용)
  • AvatarGroup → AvatarGroup(그대로지만 max prop 제거됨)

Avatar.Image로 이동한 props:

  • src, srcSet, sizes, loading, referrerPolicy, crossOrigin

Avatar.Fallback로 이동한 props:

  • name — 이니셜 자동 생성
  • icon — children으로 렌더링
  • iconLabel → aria-label

제거된 props:

  • ignoreFallback — 더 이상 필요 없음
  • showBorder — border와 borderColor 스타일 prop 사용
  • AvatarGroup max — 제거됨, 사용자 코드에서 처리
  • AvatarGroup spacing → spaceX

Before:

import { Avatar, AvatarBadge, AvatarGroup } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Avatar name="Dan Abrahmov" src="https://bit.ly/dan-abramov" size="md" />

    <Avatar bg="red.500" icon={<AiOutlineUser />} />

    <Avatar>
      <AvatarBadge boxSize="1.25em" bg="green.500" />
    </Avatar>
  </>
)

After:

import { Avatar, AvatarGroup, Circle, Float } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Avatar.Root size="md">
      <Avatar.Fallback name="Dan Abrahmov" />
      <Avatar.Image src="https://bit.ly/dan-abramov" />
    </Avatar.Root>

    <Avatar.Root bg="red.500">
      <Avatar.Fallback>
        <AiOutlineUser />
      </Avatar.Fallback>
    </Avatar.Root>

    <Avatar.Root>
      <Avatar.Image src="https://bit.ly/dan-abramov" />
      <Float placement="bottom-end" offsetX="1" offsetY="1">
        <Circle
          bg="green.500"
          size="8px"
          outline="0.2em solid"
          outlineColor="bg"
        />
      </Float>
    </Avatar.Root>
  </>
)

항목 사이에 명시적 구분자와 필수 Breadcrumb.List 래퍼가 있는 컴파운드 컴포넌트를 사용합니다.

컴포넌트 이름 변경:

  • Breadcrumb → Breadcrumb.Root
  • BreadcrumbItem → Breadcrumb.Item
  • BreadcrumbLink → Breadcrumb.Link
  • isCurrentPage를 가진 BreadcrumbLink → Breadcrumb.CurrentLink
  • BreadcrumbSeparator → Breadcrumb.Separator

Prop 변화:

  • separator prop → 제거됨, 항목 사이에 명시적 <Breadcrumb.Separator /> 사용
  • spacing → gap(Breadcrumb.List로 이동)
  • BreadcrumbItem의 isCurrentPage → Breadcrumb.CurrentLink 사용
  • isLastChild → 제거됨(명시적 구분자로 불필요)
  • listProps → Breadcrumb.List에 직접 spread

Before:

import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from "@chakra-ui/react"

const Demo = () => (
  <Breadcrumb separator="-" spacing="8px">
    <BreadcrumbItem>
      <BreadcrumbLink href="#">Home</BreadcrumbLink>
    </BreadcrumbItem>
    <BreadcrumbItem isCurrentPage>
      <BreadcrumbLink href="#">Current</BreadcrumbLink>
    </BreadcrumbItem>
  </Breadcrumb>
)

After:

import { Breadcrumb } from "@chakra-ui/react"

const Demo = () => (
  <Breadcrumb.Root>
    <Breadcrumb.List gap="8px">
      <Breadcrumb.Item>
        <Breadcrumb.Link href="#">Home</Breadcrumb.Link>
      </Breadcrumb.Item>
      <Breadcrumb.Separator>-</Breadcrumb.Separator>
      <Breadcrumb.Item>
        <Breadcrumb.CurrentLink>Current</Breadcrumb.CurrentLink>
      </Breadcrumb.Item>
    </Breadcrumb.List>
  </Breadcrumb.Root>
)

Portal

  • containerRef를 사용하는 방식으로 appendToParentPortal prop을 제거했어요.
  • PortalManager 컴포넌트를 제거했어요.

Progress

  • Progress.Root, Progress.Track, Progress.Range가 있는 컴파운드 컴포넌트를 사용합니다.
  • hasStripe prop은 striped로 이름이 바뀌었습니다.
  • isAnimated prop은 animated로 이름이 바뀌었습니다.
  • colorScheme prop은 colorPalette로 이름이 바뀌었습니다.

Before:

<Progress hasStripe isAnimated value={75} colorScheme="blue" />

After:

<Progress.Root striped animated value={75} colorPalette="blue">
  <Progress.Track>
    <Progress.Range />
  </Progress.Track>
</Progress.Root>

Stack

  • spacing을 gap으로 변경했어요.
  • StackItem을 제거하고 Box 컴포넌트를 직접 사용합니다.

Select

이제 NativeSelect라고 하며 모든 부분을 노출합니다.

Before:

<Select placeholder="Select option">
  <option value="option1">Option 1</option>
  <option value="option2">Option 2</option>
  <option value="option3">Option 3</option>
</Select>

After:

<NativeSelect.Root size="sm" width="240px">
  <NativeSelect.Field placeholder="Select option">
    <option value="option1">Option 1</option>
    <option value="option2">Option 2</option>
    <option value="option3">Option 3</option>
  </NativeSelect.Field>
  <NativeSelect.Indicator />
</NativeSelect.Root>

아이콘 변경하기

Before:

<Select icon={<MdArrowDropDown />} placeholder="Woohoo! A new icon" />

After:

<NativeSelect.Indicator>
  <MdArrowDropDown />
</NativeSelect.Indicator>

Collapse

  • Collapse를 Collapsible 네임스페이스로 이름을 바꿨어요.
  • in을 open으로 이름을 바꿨어요.
  • animateOpacity를 제거하고 키프레임 애니메이션 expand-height와 collapse-height를 사용하세요.

Before

<Collapse in={isOpen} animateOpacity>
  Some content
</Collapse>

After

<Collapsible.Root open={isOpen}>
  <Collapsible.Content>Some content</Collapsible.Content>
</Collapsible.Root>

Image

이제 내장 fallback 로직 없이 네이티브 img를 렌더링합니다. Img는 Image로 통합되었어요.

  • Img → Image
  • fit → objectFit
  • align → objectPosition
  • fallbackSrc, fallback, ignoreFallback, fallbackStrategy → 제거됨
  • useImage hook → 제거됨

Before:

import { Img } from "@chakra-ui/react"

const Demo = () => (
  <Img
    src="photo.jpg"
    fit="cover"
    align="center"
    fallbackSrc="placeholder.jpg"
  />
)

After:

import { Image } from "@chakra-ui/react"

const Demo = () => (
  <Image src="photo.jpg" objectFit="cover" objectPosition="center" />
)

fallback 동작은 네이티브 onError 이벤트를 사용해 src를 교체하세요.

PinInput

이제 컴파운드 컴포넌트를 사용합니다. 각 입력은 index prop을 요구하며 PinInput.Control로 감싸야 해요.

컴포넌트 이름 변경:

  • PinInput → PinInput.Root
  • PinInputField → PinInput.Input(index prop 요구)

Prop 변화:

  • value / defaultValue → 이제 string 대신 string[]
  • onChange → onValueChange({ value, valueAsString }을 받음)
  • onComplete → onValueComplete({ value, valueAsString }을 받음)
  • isDisabled → disabled
  • isInvalid → invalid
  • manageFocus → 제거됨

Before:

import { PinInput, PinInputField } from "@chakra-ui/react"

const Demo = () => (
  <PinInput defaultValue="23" onChange={setValue} onComplete={handleComplete}>
    <PinInputField />
    <PinInputField />
    <PinInputField />
  </PinInput>
)

After:

import { PinInput } from "@chakra-ui/react"

const Demo = () => (
  <PinInput.Root
    defaultValue={["2", "3"]}
    onValueChange={(e) => setValue(e.value)}
    onValueComplete={(e) => handleComplete(e.value)}
  >
    <PinInput.HiddenInput />
    <PinInput.Control>
      <PinInput.Input index={0} />
      <PinInput.Input index={1} />
      <PinInput.Input index={2} />
    </PinInput.Control>
  </PinInput.Root>
)

Popover

콘텐츠 주위에 명시적 Popover.Positioner 래퍼가 있는 컴파운드 컴포넌트를 사용합니다. PopoverTrigger는 이제 asChild를 요구합니다.

컴포넌트 이름 변경:

  • Popover → Popover.Root
  • PopoverTrigger → Popover.Trigger(asChild 추가)
  • PopoverContent → Popover.Content(Popover.Positioner로 감쌈)
  • PopoverHeader → Popover.Title
  • PopoverBody → Popover.Body
  • PopoverFooter → Popover.Footer
  • PopoverArrow → Popover.Arrow
  • PopoverCloseButton → Popover.CloseTrigger
  • PopoverAnchor → Popover.Anchor

Prop 변화:

  • isOpen → open
  • defaultIsOpen → defaultOpen
  • onClose / onOpen → onOpenChange({ open }을 받음)
  • closeOnBlur → closeOnInteractOutside
  • closeOnEsc → closeOnEscape
  • isLazy → lazyMount
  • lazyBehavior="unmount" → unmountOnExit
  • initialFocusRef → initialFocusEl={() => ref.current}
  • trigger="hover" → HoverCard 컴포넌트 사용
  • 위치 지정 props(placement, gutter, flip, offset, matchWidth, strategy) → positioning 객체로 그룹화
  • matchWidth → positioning.sameWidth

제거된 props: computePositionOnMount, returnFocusOnClose, arrowShadowColor, modifiers

Before:

import {
  Popover,
  PopoverArrow,
  PopoverBody,
  PopoverCloseButton,
  PopoverContent,
  PopoverHeader,
  PopoverTrigger,
} from "@chakra-ui/react"

const Demo = () => (
  <Popover placement="bottom" closeOnBlur={false} isLazy>
    <PopoverTrigger>
      <Button>Trigger</Button>
    </PopoverTrigger>
    <PopoverContent>
      <PopoverArrow />
      <PopoverCloseButton />
      <PopoverHeader>Title</PopoverHeader>
      <PopoverBody>Content here</PopoverBody>
    </PopoverContent>
  </Popover>
)

After:

import { Popover } from "@chakra-ui/react"

const Demo = () => (
  <Popover.Root
    positioning={{ placement: "bottom" }}
    closeOnInteractOutside={false}
    lazyMount
  >
    <Popover.Trigger asChild>
      <Button>Trigger</Button>
    </Popover.Trigger>
    <Popover.Positioner>
      <Popover.Content>
        <Popover.Arrow />
        <Popover.CloseTrigger />
        <Popover.Title>Title</Popover.Title>
        <Popover.Body>Content here</Popover.Body>
      </Popover.Content>
    </Popover.Positioner>
  </Popover.Root>
)

Hover 트리거 → HoverCard:

trigger="hover"를 사용했다면 HoverCard 컴포넌트로 마이그레이션하세요.

Before:

<Popover trigger="hover" openDelay={500}>
  <PopoverTrigger>
    <Button>Hover me</Button>
  </PopoverTrigger>
  <PopoverContent>
    <PopoverBody>Tooltip-like content</PopoverBody>
  </PopoverContent>
</Popover>

After:

import { HoverCard } from "@chakra-ui/react"

const Demo = () => (
  <HoverCard.Root openDelay={500}>
    <HoverCard.Trigger asChild>
      <Button>Hover me</Button>
    </HoverCard.Trigger>
    <HoverCard.Positioner>
      <HoverCard.Content>
        <HoverCard.Arrow />
        Content here
      </HoverCard.Content>
    </HoverCard.Positioner>
  </HoverCard.Root>
)

NumberInput

컴포넌트 이름 변경:

  • NumberInput → NumberInput.Root
  • NumberInputField → NumberInput.Input
  • NumberInputStepper → NumberInput.Control
  • NumberIncrementStepper → NumberInput.IncrementTrigger
  • NumberDecrementStepper → NumberInput.DecrementTrigger

Prop 변화:

  • isDisabled → disabled
  • isInvalid → invalid
  • isReadOnly → readOnly
  • isRequired → required
  • onChange → onValueChange({ value, valueAsNumber }을 받음)
  • onInvalid → onValueInvalid
  • keepWithinRange → allowOverflow(반전: false → true)
  • focusBorderColor / errorBorderColor → --focus-color / --error-color CSS 변수 사용
  • parse와 format → 제거됨, formatOptions 사용

Before:

import {
  NumberDecrementStepper,
  NumberIncrementStepper,
  NumberInput,
  NumberInputField,
  NumberInputStepper,
} from "@chakra-ui/react"

const Demo = () => (
  <NumberInput
    isDisabled
    onChange={(valStr, valNum) => {}}
    keepWithinRange={false}
  >
    <NumberInputField />
    <NumberInputStepper>
      <NumberIncrementStepper />
      <NumberDecrementStepper />
    </NumberInputStepper>
  </NumberInput>
)

After:

import { NumberInput } from "@chakra-ui/react"

const Demo = () => (
  <NumberInput.Root disabled onValueChange={(e) => {}} allowOverflow>
    <NumberInput.Input />
    <NumberInput.Control>
      <NumberInput.IncrementTrigger />
      <NumberInput.DecrementTrigger />
    </NumberInput.Control>
  </NumberInput.Root>
)

Divider

시맨틱 HTML과 ARIA 표준에 더 잘 맞도록 Separator로 이름을 바꿨습니다. 컴포넌트는 이제 더 나은 레이아웃 제어를 위해 div 요소를 사용합니다.

  • Divider → Separator
  • 스타일링은 borderTopWidth와 borderInlineStartWidth에 의존합니다.
  • 두께를 변경하려면 --divider-border-width CSS 변수를 설정하세요.
  • 모든 props(orientation, variant, 스타일링)는 동일하게 유지됩니다.

Before:

import { Divider } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Divider orientation="horizontal" />
    <Divider orientation="vertical" height="20px" />
  </>
)

After:

import { Separator } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Separator orientation="horizontal" />
    <Separator orientation="vertical" height="20px" />
  </>
)

Card

점 표기법의 컴파운드 컴포넌트를 사용합니다. 마이그레이션은 간단합니다—컴포넌트 이름만 바뀌고 모든 props는 동일합니다.

컴포넌트 이름 변경:

  • Card → Card.Root
  • CardHeader → Card.Header
  • CardBody → Card.Body
  • CardFooter → Card.Footer

v3는 더 나은 구조를 위해 Card.Title과 Card.Description이라는 새 시맨틱 컴포넌트도 도입해요.

Before:

import {
  Button,
  Card,
  CardBody,
  CardFooter,
  CardHeader,
  Heading,
  Text,
} from "@chakra-ui/react"

const Demo = () => (
  <Card maxW="sm">
    <CardHeader>
      <Heading size="md">Living room Sofa</Heading>
    </CardHeader>
    <CardBody>
      <Text>This sofa is perfect for modern tropical spaces.</Text>
      <Text color="blue.600" fontSize="2xl">
        $450
      </Text>
    </CardBody>
    <CardFooter>
      <Button variant="solid" colorScheme="blue">
        Buy now
      </Button>
    </CardFooter>
  </Card>
)

After:

import { Button, Card, Heading, Text } from "@chakra-ui/react"

const Demo = () => (
  <Card.Root maxW="sm">
    <Card.Header>
      <Heading size="md">Living room Sofa</Heading>
    </Card.Header>
    <Card.Body>
      <Text>This sofa is perfect for modern tropical spaces.</Text>
      <Text color="blue.600" fontSize="2xl">
        $450
      </Text>
    </Card.Body>
    <Card.Footer>
      <Button variant="solid" colorPalette="blue">
        Buy now
      </Button>
    </Card.Footer>
  </Card.Root>
)

Input, Select, Textarea

  • 컴포넌트를 Field 컴포넌트로 감싸는 방식으로 invalid prop을 제거했어요. 이렇게 하면 레이블, 오류 텍스트, 별표를 쉽게 추가할 수 있습니다.

Before

<Input invalid />

After

<Field.Root invalid>
  <Field.Label>Email</Field.Label>
  <Input />
  <Field.ErrorText>This field is required</Field.ErrorText>
</Field.Root>
  • target과 rel props를 명시적으로 설정하는 방식으로 isExternal prop을 제거했어요.

Before

<Link isExternal>Click me</Link>

After

<Link target="_blank" rel="noopener noreferrer">
  Click me
</Link>

List

점 표기법의 컴파운드 컴포넌트를 사용합니다. OrderedList와 UnorderedList는 더 이상 별도 컴포넌트가 아니며 as prop과 함께 List.Root를 사용하세요.

컴포넌트 이름 변경:

  • List → List.Root
  • OrderedList → List.Root as="ol"
  • UnorderedList → List.Root as="ul"
  • ListItem → List.Item
  • ListIcon → List.Indicator

Prop 변화:

  • spacing → gap
  • styleType → listStyleType
  • stylePosition → listStylePosition

Before:

import { ListIcon, ListItem, UnorderedList } from "@chakra-ui/react"
import { MdCheckCircle } from "react-icons/md"

const Demo = () => (
  <UnorderedList spacing={3}>
    <ListItem>
      <ListIcon as={MdCheckCircle} color="green.500" />
      Lorem ipsum dolor sit amet
    </ListItem>
    <ListItem>
      <ListIcon as={MdCheckCircle} color="green.500" />
      Consectetur adipiscing elit
    </ListItem>
  </UnorderedList>
)

After:

import { List } from "@chakra-ui/react"
import { MdCheckCircle } from "react-icons/md"

const Demo = () => (
  <List.Root as="ul" gap={3}>
    <List.Item>
      <List.Indicator as={MdCheckCircle} color="green.500" />
      Lorem ipsum dolor sit amet
    </List.Item>
    <List.Item>
      <List.Indicator as={MdCheckCircle} color="green.500" />
      Consectetur adipiscing elit
    </List.Item>
  </List.Root>
)

순서 목록의 경우:

Before:

import { ListItem, OrderedList } from "@chakra-ui/react"

const Demo = () => (
  <OrderedList styleType="lower-roman" stylePosition="inside">
    <ListItem>First item</ListItem>
    <ListItem>Second item</ListItem>
  </OrderedList>
)

After:

import { List } from "@chakra-ui/react"

const Demo = () => (
  <List.Root as="ol" listStyleType="lower-roman" listStylePosition="inside">
    <List.Item>First item</List.Item>
    <List.Item>Second item</List.Item>
  </List.Root>
)

Button

Prop 변화:

  • isActive → data-active 속성
  • isDisabled → disabled
  • isLoading → loading
  • colorScheme → colorPalette
  • leftIcon / rightIcon → 아이콘을 children으로 직접 렌더링
  • iconSpacing → gap
  • variant="unstyled" → unstyled 불리언 prop
  • variant="link" → variant="plain"

Before:

<Button colorScheme="blue" isLoading leftIcon={<Download />} iconSpacing={2}>
  Download
</Button>

After:

<Button colorPalette="blue" loading gap={2}>
  <Download />
  Download
</Button>

ButtonGroup 변화:

  • isAttached → attached
  • isDisabled → 제거됨(각 자식에 disabled를 전파)

IconButton

  • icon → children으로 직접 렌더링
  • isRound → borderRadius="full"
  • isDisabled → disabled

Before:

<IconButton icon={<SearchIcon />} isRounded isDisabled aria-label="Search" />

After:

<IconButton borderRadius="full" disabled aria-label="Search">
  <SearchIcon />
</IconButton>

Spinner

  • thickness prop을 borderWidth로 변경
  • speed prop을 animationDuration으로 변경

Before

<Spinner thickness="2px" speed="0.5s" />

After

<Spinner borderWidth="2px" animationDuration="0.5s" />

Dialog, Drawer

Modal과 Drawer 모두 명시적 Positioner와 Portal 래퍼가 있는 컴파운드 컴포넌트를 사용합니다.

Prop 변화(Dialog와 Drawer 공통):

  • isOpen → open
  • onClose → onOpenChange({ open }을 받음)
  • blockScrollOnMount → preventScroll
  • closeOnEsc → closeOnEscape
  • closeOnOverlayClick → closeOnInteractOutside
  • onOverlayClick → onInteractOutside
  • onEsc → onEscapeKeyDown
  • onCloseComplete → onExitComplete
  • initialFocusRef → initialFocusEl={() => ref.current}
  • finalFocusRef → finalFocusEl={() => ref.current}
  • isCentered → placement="center"(Dialog만)
  • 크기 2xl–6xl → xl에 매핑

Drawer 고유 변화:

  • placement="left" → placement="start"(RTL 인식)
  • placement="right" → placement="end"(RTL 인식)
  • isFullHeight → Drawer.Content에 height="100%" 추가
  • DrawerOverlay → Drawer.Backdrop
  • DrawerContent → Drawer.Positioner + Drawer.Content

제거된 props: allowPinchZoom, lockFocusAcrossFrames, preserveScrollBarGap, returnFocusOnClose, useInert, portalProps

Dialog 예시:

Before:

import {
  Modal,
  ModalBody,
  ModalCloseButton,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
} from "@chakra-ui/react"

const Demo = () => (
  <Modal isOpen={isOpen} onClose={onClose} isCentered closeOnEsc={false}>
    <ModalOverlay />
    <ModalContent>
      <ModalCloseButton />
      <ModalHeader>Title</ModalHeader>
      <ModalBody>Content</ModalBody>
      <ModalFooter>
        <Button onClick={onClose}>Close</Button>
      </ModalFooter>
    </ModalContent>
  </Modal>
)

After:

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

const Demo = () => (
  <Dialog.Root
    open={isOpen}
    onOpenChange={(e) => !e.open && onClose()}
    placement="center"
    closeOnEscape={false}
  >
    <Portal>
      <Dialog.Backdrop />
      <Dialog.Positioner>
        <Dialog.Content>
          <Dialog.CloseTrigger />
          <Dialog.Header>Title</Dialog.Header>
          <Dialog.Body>Content</Dialog.Body>
          <Dialog.Footer>
            <Button onClick={onClose}>Close</Button>
          </Dialog.Footer>
        </Dialog.Content>
      </Dialog.Positioner>
    </Portal>
  </Dialog.Root>
)

Drawer 예시:

Before:

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

const Demo = () => (
  <Drawer isOpen={isOpen} placement="right" onClose={onClose} isFullHeight>
    <DrawerOverlay />
    <DrawerContent>
      <DrawerCloseButton />
      <DrawerHeader>Title</DrawerHeader>
      <DrawerBody>Content</DrawerBody>
      <DrawerFooter>
        <Button onClick={onClose}>Close</Button>
      </DrawerFooter>
    </DrawerContent>
  </Drawer>
)

After:

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

const Demo = () => (
  <Drawer.Root
    open={isOpen}
    placement="end"
    onOpenChange={(e) => !e.open && onClose()}
  >
    <Portal>
      <Drawer.Backdrop />
      <Drawer.Positioner>
        <Drawer.Content height="100%">
          <Drawer.CloseTrigger />
          <Drawer.Header>Title</Drawer.Header>
          <Drawer.Body>Content</Drawer.Body>
          <Drawer.Footer>
            <Button onClick={onClose}>Close</Button>
          </Drawer.Footer>
        </Drawer.Content>
      </Drawer.Positioner>
    </Portal>
  </Drawer.Root>
)

Editable

점 표기법의 컴파운드 컴포넌트를 사용합니다. 커스텀 컨트롤은 useEditableControls prop-getter 패턴 대신 선언적 트리거 컴포넌트를 사용합니다.

컴포넌트 이름 변경:

  • Editable → Editable.Root
  • EditablePreview → Editable.Preview
  • EditableInput → Editable.Input
  • EditableTextarea → Editable.Textarea
  • useEditableControls → useEditableContext

Prop 변화:

  • isDisabled → disabled
  • onChange → onValueChange({ value } 객체를 받음)
  • onSubmit → onValueCommit
  • onCancel → onValueRevert
  • startWithEditView → defaultEdit
  • selectAllOnFocus → selectOnFocus
  • submitOnBlur={false} → submitMode="enter"
  • finalFocusRef → finalFocusEl(요소를 반환하는 함수)
  • isPreviewFocusable={false} → Editable.Preview에 tabIndex={undefined} 추가

Before:

import { Editable, EditableInput, EditablePreview } from "@chakra-ui/react"

const Demo = () => (
  <Editable
    defaultValue="Hello"
    isDisabled
    onSubmit={handleSubmit}
    onChange={handleChange}
    submitOnBlur={false}
    startWithEditView
  >
    <EditablePreview />
    <EditableInput />
  </Editable>
)

After:

import { Editable } from "@chakra-ui/react"

const Demo = () => (
  <Editable.Root
    defaultValue="Hello"
    disabled
    onValueCommit={handleSubmit}
    onValueChange={handleChange}
    submitMode="enter"
    defaultEdit
  >
    <Editable.Preview />
    <Editable.Input />
  </Editable.Root>
)

커스텀 컨트롤 (Custom Controls):

useEditableControls prop-getter 패턴은 선언적 트리거 컴포넌트로 대체되었습니다.

Before:

function EditableControls() {
  const { isEditing, getSubmitButtonProps, getCancelButtonProps } =
    useEditableControls()
  return isEditing ? (
    <ButtonGroup size="sm">
      <IconButton icon={<CheckIcon />} {...getSubmitButtonProps()} />
      <IconButton icon={<CloseIcon />} {...getCancelButtonProps()} />
    </ButtonGroup>
  ) : null
}

After:

<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>

FormControl

표준 폼 컨트롤에는 Field, 그룹 컨트롤(라디오 그룹, 체크박스 그룹)에는 Fieldset으로 대체되었습니다. as='fieldset' 패턴은 전용 Fieldset 컴포넌트로 대체되었습니다.

컴포넌트 이름 변경:

  • FormControl → Field.Root
  • FormLabel → Field.Label
  • FormHelperText → Field.HelperText
  • FormErrorMessage → Field.ErrorText

fieldset 사용의 경우:

  • FormControl as='fieldset' → Fieldset.Root
  • FormLabel as='legend' → Fieldset.Legend
  • FormHelperText → Fieldset.HelperText
  • FormErrorMessage → Fieldset.ErrorText

Prop 변화:

  • isInvalid → invalid
  • isRequired → required
  • isDisabled → disabled
  • isReadOnly → readOnly

Before:

import {
  FormControl,
  FormErrorMessage,
  FormHelperText,
  FormLabel,
} from "@chakra-ui/react"

const Demo = () => (
  <FormControl isInvalid={isError}>
    <FormLabel>Email</FormLabel>
    <FormHelperText>We'll never share your email.</FormHelperText>
    <FormErrorMessage>Email is required.</FormErrorMessage>
  </FormControl>
)

After:

import { Field } from "@chakra-ui/react"

const Demo = () => (
  <Field.Root invalid={isError}>
    <Field.Label>Email</Field.Label>
    <Field.HelperText>We'll never share your email.</Field.HelperText>
    <Field.ErrorText>Email is required.</Field.ErrorText>
  </Field.Root>
)

Field.ErrorText는 invalid가 true일 때만 렌더링되므로 조건부 로직이 필요 없어요.

Fieldset 사용:

Before:

import { FormControl, FormHelperText, FormLabel } from "@chakra-ui/react"

const Demo = () => (
  <FormControl as="fieldset">
    <FormLabel as="legend">Favorite Character</FormLabel>
    <FormHelperText>Select only if you're a fan.</FormHelperText>
  </FormControl>
)

After:

import { Fieldset } from "@chakra-ui/react"

const Demo = () => (
  <Fieldset.Root>
    <Fieldset.Legend>Favorite Character</Fieldset.Legend>
    <Fieldset.HelperText>Select only if you're a fan.</Fieldset.HelperText>
  </Fieldset.Root>
)

Collapse

Collapsible 컴포넌트로 대체하세요.

Before:

<Collapse in={isOpen} animateOpacity>
  Some content
</Collapse>

After:

<Collapsible.Root open={isOpen}>
  <Collapsible.Content>Some content</Collapsible.Content>
</Collapsible.Root>

Fade, ScaleFade, Slide, SlideFade

모든 트랜지션 컴포넌트가 JavaScript 기반 트랜지션 대신 CSS 기반 애니메이션을 사용하는 통합 Presence 컴포넌트로 대체되었습니다.

컴포넌트 매핑:

  • Fade → animationName={{ _open: "fade-in", _closed: "fade-out" }}를 가진 Presence
  • ScaleFade → animationStyle={{ _open: "scale-fade-in", _closed: "scale-fade-out" }}를 가진 Presence
  • SlideFade → animationName={{ _open: "slide-from-bottom, fade-in", _closed: "slide-to-bottom, fade-out" }}를 가진 Presence
  • Slide → 방향별 위치 지정과 애니메이션을 가진 Presence

Prop 변화:

  • in → present
  • initialScale → 제거됨(스케일은 CSS 키프레임에서 고정)
  • offsetX / offsetY → 제거됨(오프셋은 CSS 키프레임에서 고정)
  • direction → 위치 지정 props와 방향별 애니메이션 이름으로 대체

Before:

import { Fade, Slide } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Fade in={isOpen}>
      <Box>Fading content</Box>
    </Fade>

    <Slide direction="bottom" in={isOpen}>
      <Box>Sliding content</Box>
    </Slide>
  </>
)

After:

import { Presence } from "@chakra-ui/react"

const Demo = () => (
  <>
    <Presence
      present={isOpen}
      animationName={{ _open: "fade-in", _closed: "fade-out" }}
      animationDuration="moderate"
    >
      <Box>Fading content</Box>
    </Presence>

    <Presence
      present={isOpen}
      position="fixed"
      bottom="0"
      insetX="0"
      animationName={{
        _open: "slide-from-bottom-full",
        _closed: "slide-to-bottom-full",
      }}
      animationDuration="moderate"
    >
      <Box>Sliding content</Box>
    </Presence>
  </>
)

Slide 방향 매핑:

방향 위치 지정 열기 애니메이션 닫기 애니메이션
top position="fixed" top="0" insetX="0" slide-from-top-full slide-to-top-full
bottom position="fixed" bottom="0" insetX="0" slide-from-bottom-full slide-to-bottom-full
left position="fixed" left="0" insetY="0" slide-from-left-full slide-to-left-full
right position="fixed" right="0" insetY="0" slide-from-right-full slide-to-right-full

Slider / RangeSlider

RangeSlider가 Slider와 통합되었어요—범위 모드에는 배열 값을 전달하세요. 둘 다 Slider.Control 래퍼와 각 thumb 안의 Slider.HiddenInput이 필요합니다.

컴포넌트 이름 변경:

  • Slider / RangeSlider → Slider.Root
  • SliderTrack / RangeSliderTrack → Slider.Track
  • SliderFilledTrack / RangeSliderFilledTrack → Slider.Range
  • SliderThumb / RangeSliderThumb → Slider.Thumb

Prop 변화:

  • onChange → onValueChange({ value }을 받음)
  • onChangeEnd → onValueChangeEnd({ value }을 받음)
  • onChangeStart → 제거됨
  • colorScheme → colorPalette
  • isReversed / reversed → 제거됨(dir="rtl" 사용)
  • focusThumbOnChange → 제거됨

Before:

import {
  RangeSlider,
  RangeSliderFilledTrack,
  RangeSliderThumb,
  RangeSliderTrack,
} from "@chakra-ui/react"

const Demo = () => (
  <RangeSlider defaultValue={[10, 30]} onChange={(val) => console.log(val)}>
    <RangeSliderTrack>
      <RangeSliderFilledTrack />
    </RangeSliderTrack>
    <RangeSliderThumb index={0} />
    <RangeSliderThumb index={1} />
  </RangeSlider>
)

After:

import { Slider } from "@chakra-ui/react"

const Demo = () => (
  <Slider.Root
    defaultValue={[10, 30]}
    onValueChange={(e) => console.log(e.value)}
  >
    <Slider.Control>
      <Slider.Track>
        <Slider.Range />
      </Slider.Track>
      <Slider.Thumb index={0}>
        <Slider.HiddenInput />
      </Slider.Thumb>
      <Slider.Thumb index={1}>
        <Slider.HiddenInput />
      </Slider.Thumb>
    </Slider.Control>
  </Slider.Root>
)

Table

  • TableContainer는 이제 Table.ScrollArea예요.
  • Td(이제 Table.Cell)의 isNumeric은 이제 textAlign="end"예요.
  • Th는 이제 Table.ColumnHeader예요.

컴파운드 컴포넌트 이름이 약간 변경되었습니다.

Before:

<Table variant="simple">
  <TableCaption>Imperial to metric conversion factors</TableCaption>
  <Thead>
    <Tr>
      <Th>Product</Th>
      <Th>Category</Th>
      <Th isNumeric>Price</Th>
    </Tr>
  </Thead>
  <Tbody>
    {items.map((item) => (
      <Tr key={item.id}>
        <Td>{item.name}</Td>
        <Td>{item.category}</Td>
        <Td isNumeric>{item.price}</Td>
      </Tr>
    ))}
  </Tbody>
  <Tfoot>
    <Tr>
      <Th>Product</Th>
      <Th>Category</Th>
      <Th isNumeric>Price</Th>
    </Tr>
  </Tfoot>
</Table>

After:

<Table.Root size="sm">
  <Table.Header>
    <Table.Row>
      <Table.ColumnHeader>Product</Table.ColumnHeader>
      <Table.ColumnHeader>Category</Table.ColumnHeader>
      <Table.ColumnHeader textAlign="end">Price</Table.ColumnHeader>
    </Table.Row>
  </Table.Header>
  <Table.Body>
    {items.map((item) => (
      <Table.Row key={item.id}>
        <Table.Cell>{item.name}</Table.Cell>
        <Table.Cell>{item.category}</Table.Cell>
        <Table.Cell textAlign="end">{item.price}</Table.Cell>
      </Table.Row>
    ))}
  </Table.Body>
</Table.Root>

Tag

TagLeftIcon과 TagRightIcon은 이제 Tag.StartElement와 Tag.EndElement예요.

Before:

<Tag>
  <TagLeftIcon boxSize="12px" as={AddIcon} />
  <TagLabel>Cyan</TagLabel>
  <TagRightIcon boxSize="12px" as={AddIcon} />
</Tag>

After:

<Tag.Root>
  <Tag.StartElement>
    <AddIcon />
  </Tag.StartElement>
  <Tag.Label>Cyan</Tag.Label>
  <Tag.EndElement>
    <AddIcon />
  </Tag.EndElement>
</Tag.Root>
  • TagCloseButton은 이제 Tag.CloseTrigger예요.

Before:

<Tag>
  <TagLabel>Green</TagLabel>
  <TagCloseButton />
</Tag>

After:

<Tag.Root>
  <Tag.Label>Green</Tag.Label>
  <Tag.CloseTrigger />
</Tag.Root>

Alert

점 표기법의 컴파운드 컴포넌트를 사용합니다. v3는 제목과 설명을 위한 래퍼로 Alert.Content도 도입해요.

컴포넌트 이름 변경:

  • Alert → Alert.Root
  • AlertIcon → Alert.Indicator
  • AlertTitle → Alert.Title
  • AlertDescription → Alert.Description

Prop 변화:

  • addRole prop 제거(v3에서 role은 자동 처리)

Before:

import {
  Alert,
  AlertDescription,
  AlertIcon,
  AlertTitle,
} from "@chakra-ui/react"

const Demo = () => (
  <Alert status="error">
    <AlertIcon />
    <AlertTitle>Your browser is outdated!</AlertTitle>
    <AlertDescription>Your Chakra experience may be degraded.</AlertDescription>
  </Alert>
)

After:

import { Alert } from "@chakra-ui/react"

const Demo = () => (
  <Alert.Root status="error">
    <Alert.Indicator />
    <Alert.Content>
      <Alert.Title>Your browser is outdated!</Alert.Title>
      <Alert.Description>
        Your Chakra experience may be degraded.
      </Alert.Description>
    </Alert.Content>
  </Alert.Root>
)

변형 변화 (Variant Changes):

left-accent와 top-accent 변형이 제거되었습니다. Alert.Root의 border 스타일 props로 재현하세요.

  • left-accent → variant="subtle" + borderStartWidth="3px" + borderStartColor="colorPalette.solid"
  • top-accent → variant="subtle" + borderTopWidth="3px" + borderTopColor="colorPalette.solid"

새 변형 surface와 outline이 추가되었습니다.

Before:

<Alert status="success" variant="left-accent">
  <AlertIcon />
  Data uploaded to the server. Fire on!
</Alert>

After:

<Alert.Root
  status="success"
  variant="subtle"
  borderStartWidth="3px"
  borderStartColor="colorPalette.solid"
>
  <Alert.Indicator />
  Data uploaded to the server. Fire on!
</Alert.Root>

Skeleton

  • startColor와 endColor props는 이제 CSS 변수를 사용합니다.

Before:

<Skeleton startColor="pink.500" endColor="orange.500" />

After:

<Skeleton
  css={{
    "--start-color": "colors.pink.500",
    "--end-color": "colors.orange.500",
  }}
/>
  • isLoaded prop은 이제 loading이에요.

Before:

<Skeleton isLoaded>
  <span>Chakra ui is cool</span>
</Skeleton>

After:

<Skeleton loading={false}>
  <span>Chakra ui is cool</span>
</Skeleton>

Stepper

컴파운드 컴포넌트 패턴으로 Steps로 이름이 바뀌었어요. useSteps hook은 여전히 사용 가능하지만 API가 업데이트되었습니다.

컴포넌트 이름 변경:

  • Stepper → Steps.Root
  • Step → Steps.Item
  • StepIndicator → Steps.Indicator
  • StepStatus → Steps.Status
  • StepTitle → Steps.Title
  • StepDescription → Steps.Description
  • StepSeparator → Steps.Separator

Prop 변화:

  • index → step
  • children은 Steps.List로 감싸야 함

Hook 변화:

  • useSteps({ index }) → useSteps({ defaultStep })
  • useSteps를 사용할 때는 Steps.Root 대신 value={stepsApi}와 함께 Steps.RootProvider를 사용하세요.

Before:

import {
  Step,
  StepIcon,
  StepIndicator,
  StepNumber,
  StepSeparator,
  StepStatus,
  StepTitle,
  Stepper,
} from "@chakra-ui/react"

const Demo = () => (
  <Stepper index={1}>
    {steps.map((step, index) => (
      <Step key={index}>
        <StepIndicator>
          <StepStatus complete={<StepIcon />} incomplete={<StepNumber />} />
        </StepIndicator>
        <StepTitle>{step.title}</StepTitle>
        <StepSeparator />
      </Step>
    ))}
  </Stepper>
)

After:

import { Steps } from "@chakra-ui/react"

const Demo = () => (
  <Steps.Root step={1}>
    <Steps.List>
      {steps.map((step, index) => (
        <Steps.Item key={index}>
          <Steps.Indicator>
            <Steps.Status complete={<StepIcon />} incomplete={<StepNumber />} />
          </Steps.Indicator>
          <Steps.Title>{step.title}</Steps.Title>
          <Steps.Separator />
        </Steps.Item>
      ))}
    </Steps.List>
  </Steps.Root>
)

Stat

점 표기법의 컴파운드 컴포넌트를 사용합니다.

컴포넌트 이름 변경:

  • Stat → Stat.Root
  • StatLabel → Stat.Label
  • StatNumber → Stat.ValueText
  • StatHelpText → Stat.HelpText
  • StatArrow type="increase" → Stat.UpIndicator
  • StatArrow type="decrease" → Stat.DownIndicator
  • StatGroup → Stat.Root(안에 Stat.Root children을 중첩)

Before:

import {
  Stat,
  StatArrow,
  StatHelpText,
  StatLabel,
  StatNumber,
} from "@chakra-ui/react"

const Demo = () => (
  <Stat>
    <StatLabel>Revenue</StatLabel>
    <StatNumber>$45,670</StatNumber>
    <StatHelpText>
      <StatArrow type="increase" />
      12.5%
    </StatHelpText>
  </Stat>
)

After:

import { Stat } from "@chakra-ui/react"

const Demo = () => (
  <Stat.Root>
    <Stat.Label>Revenue</Stat.Label>
    <Stat.ValueText>$45,670</Stat.ValueText>
    <Stat.HelpText>
      <Stat.UpIndicator />
      12.5%
    </Stat.HelpText>
  </Stat.Root>
)
  • 이제 모든 곳에서 컴파운드 컴포넌트를 사용합니다.

Before:

<Menu>
  <MenuButton as={Button} rightIcon={<ChevronDownIcon />}>
    Actions
  </MenuButton>
  <MenuList>
    <MenuItem>Download</MenuItem>
    <MenuItem>Create a Copy</MenuItem>
  </MenuList>
</Menu>

After:

<Menu.Root>
  <Menu.Trigger asChild>
    <Button>
      Actions
      <ChevronDownIcon />
    </Button>
  </Menu.Trigger>
  <Portal>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item value="download">Download</Menu.Item>
        <Menu.Item value="copy">Create a Copy</Menu.Item>
      </Menu.Content>
    </Menu.Positioner>
  </Portal>
</Menu.Root>
  • 내부 상태에 접근하는 것은 이제 render prop이 아닌 Menu.Context를 통해 이루어집니다.

Before:

<Menu>
  {({ isOpen }) => (
    <>
      <MenuButton isActive={isOpen} as={Button} rightIcon={<ChevronDownIcon />}>
        {isOpen ? "Close" : "Open"}
      </MenuButton>
      <MenuList>
        <MenuItem>Download</MenuItem>
        <MenuItem onClick={() => alert("Kagebunshin")}>Create a Copy</MenuItem>
      </MenuList>
    </>
  )}
</Menu>

After:

<Menu.Root>
  <Menu.Context>
    {(menu) => (
      <Menu.Trigger asChild>
        <Button>
          {menu.open ? "Close" : "Open"}
          <ChevronDownIcon />
        </Button>
      </Menu.Trigger>
    )}
  </Menu.Context>
  <Portal>
    <Menu.Positioner>
      <Menu.Content>
        <Menu.Item value="download">Download</Menu.Item>
        <Menu.Item value="copy" onSelect={() => alert("Kagebunshin")}>
          Create a Copy
        </Menu.Item>
      </Menu.Content>
    </Menu.Positioner>
  </Portal>
</Menu.Root>
  • Menu의 isLazy prop은 Menu.Root의 lazyMount와 unmountOnExit로 나뉩니다.

  • MenuOptionGroup은 상태를 각각 처리하도록 Menu.RadioItemGroup과 Menu.CheckboxItemGroup으로 나뉩니다.

Before:

<Menu>
  <MenuButton as={Button}>Trigger</MenuButton>
  <MenuList>
    <MenuOptionGroup defaultValue="asc" title="Order" type="radio">
      <MenuItemOption value="asc">Ascending</MenuItemOption>
      <MenuItemOption value="desc">Descending</MenuItemOption>
    </MenuOptionGroup>
    <MenuDivider />
    <MenuOptionGroup title="Country" type="checkbox">
      <MenuItemOption value="email">Email</MenuItemOption>
      <MenuItemOption value="phone">Phone</MenuItemOption>
      <MenuItemOption value="country">Country</MenuItemOption>
    </MenuOptionGroup>
  </MenuList>
</Menu>

After:

<Menu.Root>
  <Menu.Trigger asChild>
    <Button>Trigger</Button>
  </Menu.Trigger>
  <Portal>
    <Menu.Positioner>
      <Menu.Content minW="10rem">
        <Menu.RadioItemGroup defaultValue="asc">
          <Menu.RadioItem value="asc">Ascending</Menu.RadioItem>
          <Menu.RadioItem value="desc">Descending</Menu.RadioItem>
        </Menu.RadioItemGroup>
        <Menu.CheckboxItemGroup defaultValue={["email"]}>
          <Menu.CheckboxItem value="email">Email</Menu.CheckboxItem>
          <Menu.CheckboxItem value="phone">Phone</Menu.CheckboxItem>
          <Menu.CheckboxItem value="country">Country</Menu.CheckboxItem>
        </Menu.CheckboxItemGroup>
      </Menu.Content>
    </Menu.Positioner>
  </Portal>
</Menu.Root>

Tooltip

이제 @chakra-ui/react 대신 @/components/ui/tooltip에서 import하는 스니펫 컴포넌트입니다.

Prop 변화:

  • label → content
  • hasArrow → showArrow
  • closeOnEsc → closeOnEscape
  • closeOnMouseDown → closeOnPointerDown
  • onOpen / onClose → onOpenChange({ open }을 받음)
  • shouldWrapChildren → children을 <span>으로 수동 감싸기
  • placement, gutter, offset, arrowPadding → positioning 객체로 그룹화

제거된 props: modifiers, motionProps, portalProps, arrowSize, arrowShadowColor

Before:

import { Tooltip } from "@chakra-ui/react"

;<Tooltip label="Info" hasArrow placement="top" closeOnEsc={false}>
  <button>Hover</button>
</Tooltip>

After:

import { Tooltip } from "@/components/ui/tooltip"

;<Tooltip
  content="Info"
  showArrow
  positioning={{ placement: "top" }}
  closeOnEscape={false}
>
  <button>Hover</button>
</Tooltip>

Accordion

점 표기법의 컴파운드 컴포넌트를 사용합니다. 모든 하위 컴포넌트는 Accordion 아래에 네임스페이스됩니다.

컴포넌트 이름 변경:

  • Accordion → Accordion.Root
  • AccordionItem → Accordion.Item(이제 value prop 필수)
  • AccordionButton → Accordion.ItemTrigger
  • AccordionIcon → Accordion.ItemIndicator
  • AccordionPanel → Accordion.ItemContent + Accordion.ItemBody

Prop 변화:

  • allowMultiple → multiple
  • allowToggle → collapsible
  • defaultIndex → defaultValue(이제 문자열 배열)
  • index → value(이제 문자열 배열)
  • onChange → onValueChange

Before:

import {
  Accordion,
  AccordionButton,
  AccordionIcon,
  AccordionItem,
  AccordionPanel,
  Box,
} from "@chakra-ui/react"

const Demo = () => (
  <Accordion allowToggle>
    <AccordionItem>
      <h2>
        <AccordionButton>
          <Box as="span" flex="1" textAlign="left">
            Section 1 title
          </Box>
          <AccordionIcon />
        </AccordionButton>
      </h2>
      <AccordionPanel pb={4}>Lorem ipsum dolor sit amet.</AccordionPanel>
    </AccordionItem>
  </Accordion>
)

After:

import { Accordion, Box } from "@chakra-ui/react"

const Demo = () => (
  <Accordion.Root collapsible>
    <Accordion.Item value="section-1">
      <h2>
        <Accordion.ItemTrigger>
          <Box as="span" flex="1" textAlign="left">
            Section 1 title
          </Box>
          <Accordion.ItemIndicator />
        </Accordion.ItemTrigger>
      </h2>
      <Accordion.ItemContent>
        <Accordion.ItemBody pb={4}>
          Lorem ipsum dolor sit amet.
        </Accordion.ItemBody>
      </Accordion.ItemContent>
    </Accordion.Item>
  </Accordion.Root>
)

Render Props → Context:

AccordionItem render prop 패턴({({ isExpanded }) => ...})이 대체되었어요. Accordion.ItemContext 컴포넌트나 useAccordionItemContext hook을 사용하세요. isExpanded 속성은 이제 expanded예요.

Before:

<AccordionItem>
  {({ isExpanded }) => (
    <>
      <AccordionButton>
        <Box flex="1" textAlign="left">
          Section title
        </Box>
        {isExpanded ? <MinusIcon /> : <AddIcon />}
      </AccordionButton>
      <AccordionPanel>Content</AccordionPanel>
    </>
  )}
</AccordionItem>

After:

<Accordion.Item value="section-1">
  <Accordion.ItemContext>
    {({ expanded }) => (
      <>
        <Accordion.ItemTrigger>
          <Box flex="1" textAlign="left">
            Section title
          </Box>
          {expanded ? <LuMinus /> : <LuPlus />}
        </Accordion.ItemTrigger>
        <Accordion.ItemContent>
          <Accordion.ItemBody>Content</Accordion.ItemBody>
        </Accordion.ItemContent>
      </>
    )}
  </Accordion.ItemContext>
</Accordion.Item>

Tabs

  • 컴포넌트 구조가 변경되었고 value prop이 이제 리스트와 패널에서 필수입니다.

Before:

<Tabs>
  <TabList>
    <Tab>One</Tab>
    <Tab>Two</Tab>
    <Tab>Three</Tab>
  </TabList>
  <TabPanels>
    <TabPanel>one!</TabPanel>
    <TabPanel>two!</TabPanel>
    <TabPanel>three!</TabPanel>
  </TabPanels>
</Tabs>

After:

<Tabs.Root>
  <Tabs.List>
    <Tabs.Trigger value="one">One</Tabs.Trigger>
    <Tabs.Trigger value="two">Two</Tabs.Trigger>
    <Tabs.Trigger value="three">Three</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="one">one!</Tabs.Content>
  <Tabs.Content value="two">two!</Tabs.Content>
  <Tabs.Content value="three">three!</Tabs.Content>
</Tabs.Root>
  • defaultIndex, index, onChange는 각각 defaultValue, value, onValueChange가 됩니다.

Before:

<Tabs defaultIndex={0} index={0} onChange={(index) => {}} />

After:

<Tabs defaultValue={0} value={0} onValueChange={({ value }) => {}} />
  • Tabs의 isLazy prop은 이제 Tabs.Root의 lazyMount와 unmountOnExit예요.

Before:

<Tabs isLazy />

After:

<Tabs.Root lazyMount unmountOnExit />

Show와 Hide

  • Show와 Hide 컴포넌트가 hideFrom와 hideBelow를 위해 제거되었습니다.

Before:

<Show below="md">
  This text appears only on screens md and smaller.
</Show>

<Hide below="md">
  This text hides at the "md" value screen width and smaller.
</Hide>

After:

<Box hideBelow="md">
  This text hides at the "md" value screen width and smaller.
</Box>

<Box hideFrom="md">
  This text appears only on screens md and larger.
</Box>

Checkbox

컴파운드 컴포넌트를 사용하도록 리팩터링되었습니다. 단일 <Checkbox>는 구조와 스타일링을 완전히 제어할 수 있도록 명시적 부분으로 나뉩니다.

Prop 변화:

  • isChecked → checked
  • isDisabled → disabled
  • isInvalid → invalid
  • isReadOnly → readOnly
  • isIndeterminate → checked="indeterminate"
  • onChange → onCheckedChange
  • colorScheme → colorPalette
  • icon → Checkbox.Control의 children으로 렌더링
  • iconColor → Checkbox.Indicator의 color
  • iconSize → Checkbox.Indicator의 boxSize
  • isFocusable → 제거됨

CheckboxGroup:

  • isDisabled → disabled
  • onChange → onValueChange
  • isNative → 제거됨

Before:

import { Checkbox } from "@chakra-ui/react"

const Demo = () => (
  <Checkbox
    isChecked={checked}
    isIndeterminate={indeterminate}
    onChange={(e) => setChecked(e.target.checked)}
    colorScheme="blue"
  >
    Accept terms
  </Checkbox>
)

After:

import { Checkbox } from "@chakra-ui/react"

const Demo = () => (
  <Checkbox.Root
    checked={indeterminate ? "indeterminate" : checked}
    onCheckedChange={(e) => setChecked(!!e.checked)}
    colorPalette="blue"
  >
    <Checkbox.HiddenInput />
    <Checkbox.Control>
      <Checkbox.Indicator />
    </Checkbox.Control>
    <Checkbox.Label>Accept terms</Checkbox.Label>
  </Checkbox.Root>
)

Radio 그룹 (Radio Group)

컴파운드 컴포넌트를 사용하도록 리팩터링되었습니다. Radio는 이제 RadioGroup.Item이며 명시적 하위 컴포넌트인 ItemHiddenInput, ItemIndicator, ItemText가 있습니다.

컴포넌트 이름 변경:

  • RadioGroup → RadioGroup.Root
  • Radio → RadioGroup.Item(필수 하위 컴포넌트 포함)

RadioGroup Prop 변화:

  • onChange → onValueChange({ value } 객체를 받음)
  • colorScheme → colorPalette

Radio Prop 변화:

  • isDisabled → disabled
  • isInvalid, isChecked, defaultChecked → 제거됨(Root에서 제어)
  • colorScheme → 항목에서 제거됨(Root에서 colorPalette 설정)
  • inputProps → RadioGroup.ItemHiddenInput에 spread

Before:

import { Radio, RadioGroup } from "@chakra-ui/react"

const Demo = () => (
  <RadioGroup defaultValue="2" onChange={(val) => setValue(val)}>
    <Radio value="1">Option 1</Radio>
    <Radio value="2">Option 2</Radio>
  </RadioGroup>
)

After:

import { RadioGroup } from "@chakra-ui/react"

const Demo = () => (
  <RadioGroup.Root defaultValue="2" onValueChange={(e) => setValue(e.value)}>
    <RadioGroup.Item value="1">
      <RadioGroup.ItemHiddenInput />
      <RadioGroup.ItemIndicator />
      <RadioGroup.ItemText>Option 1</RadioGroup.ItemText>
    </RadioGroup.Item>
    <RadioGroup.Item value="2">
      <RadioGroup.ItemHiddenInput />
      <RadioGroup.ItemIndicator />
      <RadioGroup.ItemText>Option 2</RadioGroup.ItemText>
    </RadioGroup.Item>
  </RadioGroup.Root>
)

Button Props

  • isActive → data-active 속성
  • isDisabled → disabled
  • isLoading → loading
  • leftIcon과 rightIcon → children으로 전달
  • iconSpacing → 제거됨(flex 레이아웃에서 gap 사용)
  • colorScheme → colorPalette

예시:

// Before
<Button
  isActive={true}
  isDisabled={false}
  isLoading={true}
  leftIcon={<Icon />}
  rightIcon={<Icon />}
  colorScheme="blue"
>
  Submit
</Button>

// After
<Button
  data-active=""
  disabled={false}
  loading={true}
  colorPalette="blue"
>
  <LeftIcon />
  Submit
  <RightIcon />
</Button>

Input Props

  • isDisabled → disabled
  • isInvalid → invalid
  • isReadOnly → readOnly
  • isRequired → required
  • colorScheme → colorPalette
  • focusBorderColor → CSS 변수 사용
  • errorBorderColor → CSS 변수 사용

예시:

// Before
<Input
  isDisabled={false}
  isInvalid={true}
  isReadOnly={false}
  isRequired={true}
  colorScheme="blue"
  focusBorderColor="blue.500"
  errorBorderColor="red.500"
/>

// After
<Input
  disabled={false}
  invalid={true}
  readOnly={false}
  required={true}
  colorPalette="blue"
  style={{
    "--focus-color": "blue.500",
    "--error-color": "red.500"
  }}
/>

Checkbox Props

  • isChecked → checked
  • isDisabled → disabled
  • isInvalid → invalid
  • isIndeterminate → checked="indeterminate"
  • onChange → onCheckedChange
  • colorScheme → colorPalette
  • iconColor → Checkbox.Indicator의 color
  • iconSize → Checkbox.Indicator의 boxSize
  • isFocusable → 제거됨

Modal에서 Dialog로 Props

  • isOpen → open
  • onClose → onOpenChange({ open }을 받음)
  • isCentered → placement="center"
  • closeOnOverlayClick → closeOnInteractOutside
  • closeOnEsc → closeOnEscape
  • blockScrollOnMount → preventScroll
  • onOverlayClick → onInteractOutside
  • onEsc → onEscapeKeyDown
  • onCloseComplete → onExitComplete
  • initialFocusRef → initialFocusEl(요소를 반환하는 함수)
  • finalFocusRef → finalFocusEl(요소를 반환하는 함수)
  • scrollBehavior → 변경 없음
  • motionPreset → 변경 없음
  • trapFocus → 변경 없음
  • 크기 2xl–6xl → xl에 매핑
  • 제거됨: allowPinchZoom, lockFocusAcrossFrames, preserveScrollBarGap, returnFocusOnClose, useInert, portalProps

Stack Props

  • spacing → gap
  • divider → separator
  • 다른 props는 동일합니다.

예시:

// Before
<Stack
  spacing="4"
  divider={<StackDivider />}
>
  <Box>Item 1</Box>
  <Box>Item 2</Box>
</Stack>

// After
<Stack
  gap="4"
  separator={<Separator />}
>
  <Box>Item 1</Box>
  <Box>Item 2</Box>
</Stack>

더 알아보기 (Learn more)