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):
asprop 타이핑을 완화하고asChildprop 사용을 지향합니다. 이 패턴은 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를 설정하세요.leastDestructiveRefprop을Dialog.Root컴포넌트의initialFocusEl로 설정하세요.
CircularProgress
ProgressCircle로 이름이 바뀌었고 컴파운드 컴포넌트를 사용합니다.isIndeterminate는value={null}이 됩니다.thicknessprop은--thicknessCSS 변수가 됩니다.colorprop은ProgressCircle.Range의strokeprop이 됩니다.
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->opendefaultIsOpen->defaultOpenisDisabled->disabledisInvalid->invalidisRequired->required
ColorScheme prop
colorScheme prop이 colorPalette로 변경되었어요.
Before
colorScheme은 컴포넌트 테마에서만 사용할 수 있었어요.colorScheme은 HTML 요소의 네이티브colorSchemeprop과 충돌합니다.
<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->lineClamptruncated->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
-
themeprop을 제거하고 대신systemprop을 전달해요.theme대신defaultSystem모듈을 import하세요. -
resetCssprop을 제거하고 대신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함수에 전달하세요.
Modal
Dialog로 이름이 바뀌었고 명시적 Dialog.Positioner와 Portal 래퍼가 있는 컴파운드 컴포넌트를 사용합니다.
컴포넌트 이름 변경:
Modal→Dialog.RootModalOverlay→Dialog.BackdropModalContent→Dialog.Content(Dialog.Positioner로 감쌈)ModalHeader→Dialog.HeaderModalBody→Dialog.BodyModalFooter→Dialog.FooterModalCloseButton→Dialog.CloseTrigger
Prop 변화:
isOpen→openonClose→onOpenChange({ open }을 받음)isCentered→placement="center"closeOnOverlayClick→closeOnInteractOutsidecloseOnEsc→closeOnEscapeblockScrollOnMount→preventScrollonOverlayClick→onInteractOutsideonEsc→onEscapeKeyDownonCloseComplete→onExitCompleteinitialFocusRef→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.RootAvatarBadge→ 제거됨(Float+Circle사용)AvatarGroup→AvatarGroup(그대로지만maxprop 제거됨)
Avatar.Image로 이동한 props:
src,srcSet,sizes,loading,referrerPolicy,crossOrigin
Avatar.Fallback로 이동한 props:
name— 이니셜 자동 생성icon— children으로 렌더링iconLabel→aria-label
제거된 props:
ignoreFallback— 더 이상 필요 없음showBorder—border와borderColor스타일 prop 사용AvatarGroupmax— 제거됨, 사용자 코드에서 처리AvatarGroupspacing→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
항목 사이에 명시적 구분자와 필수 Breadcrumb.List 래퍼가 있는 컴파운드 컴포넌트를 사용합니다.
컴포넌트 이름 변경:
Breadcrumb→Breadcrumb.RootBreadcrumbItem→Breadcrumb.ItemBreadcrumbLink→Breadcrumb.LinkisCurrentPage를 가진BreadcrumbLink→Breadcrumb.CurrentLinkBreadcrumbSeparator→Breadcrumb.Separator
Prop 변화:
separatorprop → 제거됨, 항목 사이에 명시적<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를 사용하는 방식으로appendToParentPortalprop을 제거했어요.PortalManager컴포넌트를 제거했어요.
Progress
Progress.Root,Progress.Track,Progress.Range가 있는 컴파운드 컴포넌트를 사용합니다.hasStripeprop은striped로 이름이 바뀌었습니다.isAnimatedprop은animated로 이름이 바뀌었습니다.colorSchemeprop은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→Imagefit→objectFitalign→objectPositionfallbackSrc,fallback,ignoreFallback,fallbackStrategy→ 제거됨useImagehook → 제거됨
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.RootPinInputField→PinInput.Input(indexprop 요구)
Prop 변화:
value/defaultValue→ 이제string대신string[]onChange→onValueChange({ value, valueAsString }을 받음)onComplete→onValueComplete({ value, valueAsString }을 받음)isDisabled→disabledisInvalid→invalidmanageFocus→ 제거됨
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.RootPopoverTrigger→Popover.Trigger(asChild추가)PopoverContent→Popover.Content(Popover.Positioner로 감쌈)PopoverHeader→Popover.TitlePopoverBody→Popover.BodyPopoverFooter→Popover.FooterPopoverArrow→Popover.ArrowPopoverCloseButton→Popover.CloseTriggerPopoverAnchor→Popover.Anchor
Prop 변화:
isOpen→opendefaultIsOpen→defaultOpenonClose/onOpen→onOpenChange({ open }을 받음)closeOnBlur→closeOnInteractOutsidecloseOnEsc→closeOnEscapeisLazy→lazyMountlazyBehavior="unmount"→unmountOnExitinitialFocusRef→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.RootNumberInputField→NumberInput.InputNumberInputStepper→NumberInput.ControlNumberIncrementStepper→NumberInput.IncrementTriggerNumberDecrementStepper→NumberInput.DecrementTrigger
Prop 변화:
isDisabled→disabledisInvalid→invalidisReadOnly→readOnlyisRequired→requiredonChange→onValueChange({ value, valueAsNumber }을 받음)onInvalid→onValueInvalidkeepWithinRange→allowOverflow(반전:false→true)focusBorderColor/errorBorderColor→--focus-color/--error-colorCSS 변수 사용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-widthCSS 변수를 설정하세요. - 모든 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.RootCardHeader→Card.HeaderCardBody→Card.BodyCardFooter→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컴포넌트로 감싸는 방식으로invalidprop을 제거했어요. 이렇게 하면 레이블, 오류 텍스트, 별표를 쉽게 추가할 수 있습니다.
Before
<Input invalid />
After
<Field.Root invalid>
<Field.Label>Email</Field.Label>
<Input />
<Field.ErrorText>This field is required</Field.ErrorText>
</Field.Root>
Link
target과relprops를 명시적으로 설정하는 방식으로isExternalprop을 제거했어요.
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.RootOrderedList→List.Root as="ol"UnorderedList→List.Root as="ul"ListItem→List.ItemListIcon→List.Indicator
Prop 변화:
spacing→gapstyleType→listStyleTypestylePosition→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→disabledisLoading→loadingcolorScheme→colorPaletteleftIcon/rightIcon→ 아이콘을 children으로 직접 렌더링iconSpacing→gapvariant="unstyled"→unstyled불리언 propvariant="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→attachedisDisabled→ 제거됨(각 자식에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
thicknessprop을borderWidth로 변경speedprop을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→openonClose→onOpenChange({ open }을 받음)blockScrollOnMount→preventScrollcloseOnEsc→closeOnEscapecloseOnOverlayClick→closeOnInteractOutsideonOverlayClick→onInteractOutsideonEsc→onEscapeKeyDownonCloseComplete→onExitCompleteinitialFocusRef→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.BackdropDrawerContent→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.RootEditablePreview→Editable.PreviewEditableInput→Editable.InputEditableTextarea→Editable.TextareauseEditableControls→useEditableContext
Prop 변화:
isDisabled→disabledonChange→onValueChange({ value }객체를 받음)onSubmit→onValueCommitonCancel→onValueRevertstartWithEditView→defaultEditselectAllOnFocus→selectOnFocussubmitOnBlur={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.RootFormLabel→Field.LabelFormHelperText→Field.HelperTextFormErrorMessage→Field.ErrorText
fieldset 사용의 경우:
FormControl as='fieldset'→Fieldset.RootFormLabel as='legend'→Fieldset.LegendFormHelperText→Fieldset.HelperTextFormErrorMessage→Fieldset.ErrorText
Prop 변화:
isInvalid→invalidisRequired→requiredisDisabled→disabledisReadOnly→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" }}를 가진PresenceScaleFade→animationStyle={{ _open: "scale-fade-in", _closed: "scale-fade-out" }}를 가진PresenceSlideFade→animationName={{ _open: "slide-from-bottom, fade-in", _closed: "slide-to-bottom, fade-out" }}를 가진PresenceSlide→ 방향별 위치 지정과 애니메이션을 가진Presence
Prop 변화:
in→presentinitialScale→ 제거됨(스케일은 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.RootSliderTrack/RangeSliderTrack→Slider.TrackSliderFilledTrack/RangeSliderFilledTrack→Slider.RangeSliderThumb/RangeSliderThumb→Slider.Thumb
Prop 변화:
onChange→onValueChange({ value }을 받음)onChangeEnd→onValueChangeEnd({ value }을 받음)onChangeStart→ 제거됨colorScheme→colorPaletteisReversed/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.RootAlertIcon→Alert.IndicatorAlertTitle→Alert.TitleAlertDescription→Alert.Description
Prop 변화:
addRoleprop 제거(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와endColorprops는 이제 CSS 변수를 사용합니다.
Before:
<Skeleton startColor="pink.500" endColor="orange.500" />
After:
<Skeleton
css={{
"--start-color": "colors.pink.500",
"--end-color": "colors.orange.500",
}}
/>
isLoadedprop은 이제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.RootStep→Steps.ItemStepIndicator→Steps.IndicatorStepStatus→Steps.StatusStepTitle→Steps.TitleStepDescription→Steps.DescriptionStepSeparator→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.RootStatLabel→Stat.LabelStatNumber→Stat.ValueTextStatHelpText→Stat.HelpTextStatArrow type="increase"→Stat.UpIndicatorStatArrow type="decrease"→Stat.DownIndicatorStatGroup→Stat.Root(안에Stat.Rootchildren을 중첩)
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>
)
Menu
- 이제 모든 곳에서 컴파운드 컴포넌트를 사용합니다.
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의isLazyprop은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→contenthasArrow→showArrowcloseOnEsc→closeOnEscapecloseOnMouseDown→closeOnPointerDownonOpen/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.RootAccordionItem→Accordion.Item(이제valueprop 필수)AccordionButton→Accordion.ItemTriggerAccordionIcon→Accordion.ItemIndicatorAccordionPanel→Accordion.ItemContent+Accordion.ItemBody
Prop 변화:
allowMultiple→multipleallowToggle→collapsibledefaultIndex→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
- 컴포넌트 구조가 변경되었고
valueprop이 이제 리스트와 패널에서 필수입니다.
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의isLazyprop은 이제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→checkedisDisabled→disabledisInvalid→invalidisReadOnly→readOnlyisIndeterminate→checked="indeterminate"onChange→onCheckedChangecolorScheme→colorPaletteicon→Checkbox.Control의 children으로 렌더링iconColor→Checkbox.Indicator의coloriconSize→Checkbox.Indicator의boxSizeisFocusable→ 제거됨
CheckboxGroup:
isDisabled→disabledonChange→onValueChangeisNative→ 제거됨
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.RootRadio→RadioGroup.Item(필수 하위 컴포넌트 포함)
RadioGroup Prop 변화:
onChange→onValueChange({ value }객체를 받음)colorScheme→colorPalette
Radio Prop 변화:
isDisabled→disabledisInvalid,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→disabledisLoading→loadingleftIcon과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→disabledisInvalid→invalidisReadOnly→readOnlyisRequired→requiredcolorScheme→colorPalettefocusBorderColor→ 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→checkedisDisabled→disabledisInvalid→invalidisIndeterminate→checked="indeterminate"onChange→onCheckedChangecolorScheme→colorPaletteiconColor→Checkbox.Indicator의coloriconSize→Checkbox.Indicator의boxSizeisFocusable→ 제거됨
Modal에서 Dialog로 Props
isOpen→openonClose→onOpenChange({ open }을 받음)isCentered→placement="center"closeOnOverlayClick→closeOnInteractOutsidecloseOnEsc→closeOnEscapeblockScrollOnMount→preventScrollonOverlayClick→onInteractOutsideonEsc→onEscapeKeyDownonCloseComplete→onExitCompleteinitialFocusRef→initialFocusEl(요소를 반환하는 함수)finalFocusRef→finalFocusEl(요소를 반환하는 함수)scrollBehavior→ 변경 없음motionPreset→ 변경 없음trapFocus→ 변경 없음- 크기
2xl–6xl→xl에 매핑 - 제거됨:
allowPinchZoom,lockFocusAcrossFrames,preserveScrollBarGap,returnFocusOnClose,useInert,portalProps
Stack Props
spacing→gapdivider→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>