开始使用
从 v2 迁移
如何将 Chakra UI 从 v2.x 迁移到 v3.x。
Codemod(推荐)
codemod 可以自动化从 Chakra UI v2 迁移到 v3 的过程。它会处理组件重命名、属性变更、导入更新以及复合组件重构。 在手动迁移之前,请从这里开始。
npx @chakra-ui/codemod upgrade使用 --dry 参数可以先预览变更,而不会修改你的文件。
手动迁移步骤
更新依赖包
移除不再使用的依赖包:@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 安装组件代码片段(snippets)。代码片段提供了 Chakra 组件的预构建组合,可以帮你节省时间,并让你把主动权掌握在自己手中。
npx @chakra-ui/cli snippet add重构自定义主题
把自定义主题移动到一个专门的 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` },
},
},
},
})所有 token 值都需要包裹在一个带有 value 键的对象中。此处可了解更多 token 相关知识: tokens。
更新 ChakraProvider
将 ChakraProvider 的导入从 @chakra-ui/react 改为使用代码片段提供的那个。接着把 theme 属性重命名为 value,以匹配新的基于 system 的主题方式。
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。
核心改进
- 性能(Performance): 协调(reconciliation)性能提升 4 倍,重渲染性能提升 1.6 倍
- 命名空间导入(Namespaced imports): 使用点记法导入组件,让导入更简洁tsx
import { Accordion } from "@chakra-ui/react" const Demo = () => { return ( <Accordion.Root> <Accordion.Item> <Accordion.ItemTrigger /> <Accordion.ItemContent /> </Accordion.Item> </Accordion.Root> ) } - TypeScript: 改善了样式属性和 token 的 IntelliSense 与类型推断。
- 多态(Polymorphism): 放宽了
as属性的类型定义,转而推荐使用asChild属性。此模式受到了 Radix Primitives 与 Ark UI 的启发。
已移除的功能
颜色模式(Color Mode)
ColorModeProvider和useColorMode已被移除,改用next-themesLightMode、DarkMode和ColorModeScript组件已被移除。现在你需要使用className="light"或className="dark"来强制主题。useColorModeValue已被移除,改用next-themes的useTheme
next-themes 快速搭建颜色模式。Hooks
我们移除了 hooks 包,转而使用更专注、更健壮的库,例如 react-use 和 usehooks-ts。
我们现在唯一提供的 hooks 是 useBreakpointValue、 useCallbackRef、useDisclosure、 useControllableState 和 useMediaQuery。
样式配置(Style Config)
我们移除了 styleConfig 和 multiStyleConfig 概念,改用 recipe 与 slot recipe。此模式借鉴了 Panda CSS。
Next.js 包
我们移除了 @chakra-ui/next-js 包,改用 asChild 属性以获得更好的灵活性。
要样式化 Next.js 的 image 组件,请在 Box 组件上使用 asChild 属性。
<Box asChild>
<NextImage />
</Box>要样式化 Next.js 的 link 组件,请在 Link 组件上使用 asChild 属性。
<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 属性被简化,我们不再提供自定义的 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 样式属性的图标 → 用
@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 → LuPlusCloseIcon → LuXCheckIcon → LuCheckEditIcon → LuPencilDeleteIcon → LuTrash2SearchIcon → LuSearchChevronDownIcon → LuChevronDownArrowForwardIcon → LuArrowRightHamburgerIcon → LuMenuWarningIcon → LuAlertTriangleInfoIcon → LuInfoExternalLinkIcon → LuExternalLinkStarIcon → LuStar
自定义 SVG 图标
在 v2 中,<Icon> 会渲染一个 <svg> 包裹,并把 SVG 子元素(例如 <path>)直接传入。在 v3 中,使用自定义 SVG 时,请使用 asChild 属性,使 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 Addon
我们移除了 storybook addon,改用 @storybook/addon-themes 和 withThemeByClassName 辅助函数。
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已移除的组件
StackItem:你不再需要它了。请改用
Box。FocusLock:我们不再自带焦点锁定组件。请直接安装并使用
react-focus-lock。AlertDialog:改用
Dialog组件并设置role="alertdialog";将leastDestructiveRef属性设置为Dialog.Root组件的initialFocusEl。
CircularProgress
已改名为
ProgressCircle,并使用复合组件isIndeterminate变为value={null}thickness属性变为--thicknessCSS 变量color属性变为ProgressCircle.Range上的stroke属性
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
不再作为独立组件提供
在 stack 项之间使用显式的
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 { Separator, VStack, Box } 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>属性变更
布尔属性(Boolean Props)
布尔属性的命名约定已从 is<X> 改为 <x>。
isOpen → opendefaultIsOpen → defaultOpenisDisabled → disabledisInvalid → invalidisRequired → required
ColorScheme 属性
colorScheme 属性已改名为 colorPalette。
Before(迁移前)
你只能在组件的主题中使用
colorSchemecolorScheme与 HTML 原生元素上的colorScheme属性相冲突
<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颜色键
theme.ts
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}" },
},
},
},
},
})在此可了解更多: 自定义颜色指南
渐变属性(Gradient Props)
渐变相关的样式属性简化为 gradient、 gradientFrom 和 gradientTo 三个属性。这降低了解析渐变字符串的运行时性能开销,并带来更好的类型推断。
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 个色阶(shades),以支持更多颜色变化。
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" },
},
}样式属性(Style Props)
一些样式属性的命名约定已更改:
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>我们移除了 apply 属性,改用 textStyle 或 layerStyles。
嵌套样式(Nested Styles)
我们改变了在 Chakra UI 组件中编写嵌套样式的方式。
Before(迁移前)
<Box
sx={{
svg: { color: "red.500" },
}}
/>After(迁移后)
<Box
css={{
"& svg": { color: "red.500" },
}}
/>这样做有两个原因:
更快的样式处理: 之前我们必须检查样式键到底是样式属性还是选择器, 整体开销相当大。
更好的类型提示: 这使得嵌套样式属性更容易被强类型化。
组件变更
ChakraProvider
移除了 theme 属性,改为传入 System 属性。导入 defaultSystem 模块,而不再导入 theme。
移除了 resetCss 属性,改为向 createSystem 函数传入 preflight: false。
不再支持配置 toast 选项,请改在 components/ui/toaster.tsx 文件中的 createToaster 函数里配置。
Before(迁移前)
<ChakraProvider resetCss={false}>
<Component />
</ChakraProvider>After(迁移后)
const system = createSystem(defaultConfig, { preflight: false })
<Provider value={system}>
<Component />
</Provider>Modal
已改名为 Dialog,并使用复合组件,带有显式的 Dialog.Positioner 和 Portal 包裹。
组件重命名:
Modal → Dialog.RootModalOverlay → Dialog.BackdropModalContent → Dialog.Content(包裹于 Dialog.Positioner 中)ModalHeader → Dialog.HeaderModalBody → Dialog.BodyModalFooter → Dialog.FooterModalCloseButton → Dialog.CloseTrigger
属性变更:
isOpen → openonClose → onOpenChange(接收 { open })isCentered → placement="center"closeOnOverlayClick → closeOnInteractOutsidecloseOnEsc → closeOnEscapeblockScrollOnMount → preventScrollonOverlayClick → onInteractOutsideonEsc → onEscapeKeyDownonCloseComplete → onExitCompleteinitialFocusRef → initialFocusEl={() => ref.current}finalFocusRef → finalFocusEl={() => ref.current}
尺寸映射: v3 中 2xl 到 6xl 的尺寸都会被映射为 xl。
已移除的属性: allowPinchZoom、lockFocusAcrossFrames、 preserveScrollBarGap、 returnFocusOnClose、useInert、 portalProps
Avatar
现在使用声明式组合模式,包含独立的 Avatar.Image 和 Avatar.Fallback 部件。
组件重命名:
Avatar → Avatar.RootAvatarBadge → 已移除(改用 Float + Circle)AvatarGroup → AvatarGroup(保持不变,但移除了 max 属性)
移入 Avatar.Image 的属性:
src、srcSet、sizes、loading、referrerPolicy、crossOrigin
移入 Avatar.Fallback 的属性:
name — 自动生成首字母缩写icon — 改为以 children 方式渲染iconLabel → aria-label
已移除的属性:
ignoreFallback — 不再需要showBorder — 改用 border 和 borderColor 样式属性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
现在使用复合组件,在条目之间使用显式分隔符,并需要一个必填的 Breadcrumb.List 包裹。
组件重命名:
Breadcrumb → Breadcrumb.RootBreadcrumbItem → Breadcrumb.ItemBreadcrumbLink → Breadcrumb.Link带 isCurrentPage 的 BreadcrumbLink → Breadcrumb.CurrentLinkBreadcrumbSeparator → Breadcrumb.Separator
属性变更:
separator → 已移除,改在条目之间使用显式分隔符组件spacing → gap(移入 Breadcrumb.List)BreadcrumbItem 上的 isCurrentPage → 改用 Breadcrumb.CurrentLinkisLastChild → 已移除(使用显式分隔符后不再需要)listProps → 直接展开到 Breadcrumb.List 上
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
移除了
appendToParentPortal属性,改用containerRef移除了
PortalManager组件
Progress
现在使用
Progress.Root、Progress.Track和Progress.Range复合组件hasStripe属性改名为stripedisAnimated属性改名为animatedcolorScheme属性改名为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组件divider改为separator
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改名为openanimateOpacity已被移除,需改用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
现在渲染原生的 img,不内置回退(fallback)逻辑。 Img 已并入 Image。
Img → Imagefit → objectFitalign → objectPositionfallbackSrc、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" />
)如需回退行为,请使用原生 onError 事件来切换 src。
PinInput
现在使用复合组件。每个 input 都需要 index 属性,并且必须包裹在 PinInput.Control 中。
组件重命名:
PinInput → PinInput.RootPinInputField → PinInput.Input(需要 index 属性)
属性变更:
value / defaultValue → 现在为 string[],不再是 stringonChange → 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
属性变更:
isOpen → opendefaultIsOpen → defaultOpenonClose / onOpen → onOpenChange(接收 { open })closeOnBlur → closeOnInteractOutsidecloseOnEsc → closeOnEscapeisLazy → lazyMountlazyBehavior="unmount" → unmountOnExitinitialFocusRef → initialFocusEl={() => ref.current}trigger="hover" → 改用 HoverCard 组件定位属性(placement、gutter、flip、offset、matchWidth、strategy)→ 合并到 positioning 对象matchWidth → positioning.sameWidth
已移除的属性: 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
属性变更:
isDisabled → disabledisInvalid → invalidisReadOnly → readOnlyisRequired → requiredonChange → onValueChange(接收 { value, valueAsNumber })onInvalid → onValueInvalidkeepWithinRange → allowOverflow(取值取反)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
已改名为 Separator,以更好地对齐语义化 HTML 和 ARIA 标准。 该组件现在使用 div 元素,以获得更好的布局控制。
Divider → Separator依赖 borderTopWidth 和 borderInlineStartWidth 进行样式处理要改变粗细,请设置 --divider-border-width CSS 变量所有属性(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
现在使用点记法的复合组件。迁移很简单——只有组件名称会改变,所有属性保持不变。
组件重命名:
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
移除了 invalid 属性,改为将组件包裹在 Field 组件中。这样可以轻松添加标签、错误文本和星号。
Before(迁移前)
<Input invalid />After(迁移后)
<Field.Root invalid>
<Field.Label>Email</Field.Label>
<Input />
<Field.ErrorText>This field is required</Field.ErrorText>
</Field.Root>Link
移除了 isExternal 属性,改为显式设置 target 和 rel 属性。
Before(迁移前)
<Link isExternal>Click me</Link>After(迁移后)
<Link target="_blank" rel="noopener noreferrer">
Click me
</Link>List
现在使用点记法的复合组件。OrderedList 和 UnorderedList 不再是独立的组件——请使用 List.Root 配合 as 属性。
组件重命名:
List → List.RootOrderedList → List.Root as="ol"UnorderedList → List.Root as="ul"ListItem → List.ItemListIcon → List.Indicator
属性变更:
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
属性变更:
isActive → data-active 属性isDisabled → disabledisLoading → loadingcolorScheme → colorPaletteleftIcon / rightIcon → 图标直接作为 children 渲染iconSpacing → gapvariant="unstyled" → unstyled 布尔属性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 → 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
将 thickness 属性改为 borderWidth将 speed 属性改为 animationDuration
Before(迁移前)
<Spinner thickness="2px" speed="0.5s" />After(迁移后)
<Spinner borderWidth="2px" animationDuration="0.5s" />Dialog、Drawer
Modal 和 Drawer 现在都使用复合组件,带有显式的 Positioner 和 Portal 包裹。
属性变更(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
已移除的属性: 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
属性变更:
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>
)自定义控件:
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 替换分组控件(radio 组、checkbox 组)。 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
属性变更:
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>
)Collapsible
用 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
所有过渡组件都被统一的 Presence 组件替代,它使用基于 CSS 的动画,而不是基于 JavaScript 的过渡。
组件映射:
Fade → Presence(animationName: fade-in / fade-out)ScaleFade → Presence(animationStyle: scale-fade-in / scale-fade-out)SlideFade → Presence(slide-from-bottom / slide-to-bottom 等)Slide → Presence(按方向设置定位与动画)
属性变更:
in → presentinitialScale → 已移除(缩放固定于 CSS keyframes)offsetX / offsetY → 已移除(偏移固定于 CSS keyframes)direction → 由定位属性与方向专属动画名称替代
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-fullbottom → position="fixed" bottom="0" insetX="0" → slide-from-bottom-full / slide-to-bottom-fullleft → position="fixed" left="0" insetY="0" → slide-from-left-full / slide-to-left-fullright → 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
属性变更:
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.ScrollAreaTd(现在叫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
属性变更:
移除了
addRole属性(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>
)变体变更:
left-accent 和 top-accent 变体已被移除。请在 Alert.Root 上使用边框样式属性来复现效果:
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 属性现在使用 CSS 变量。
Before(迁移前)
<Skeleton startColor="pink.500" endColor="orange.500" />After(迁移后)
<Skeleton
css={{
"--start-color": "colors.pink.500",
"--end-color": "colors.orange.500",
}}
/>isLoaded 属性现在叫 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
属性变更:
index → step子元素必须包裹在 Steps.List 中
Hook 变更:
useSteps({ index }) → useSteps({ defaultStep })使用 useSteps 时,用 Steps.RootProvider 传入 value={stepsApi},而不是 Steps.Root
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.Root
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>现在可以通过 Menu.Context 访问内部状态,不再使用 render prop。
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("Naruto")}>
Create a Copy
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>Menu上的isLazy属性被拆分为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
现在是一个 snippet 组件,从 @/components/ui/tooltip 导入,而不是从 @chakra-ui/react 导入。
属性变更:
label → contenthasArrow → showArrowcloseOnEsc → closeOnEscapecloseOnMouseDown → closeOnPointerDownonOpen / onClose → onOpenChange(接收 { open })shouldWrapChildren → 手动用 <span> 包裹 childrenplacement、gutter、offset、arrowPadding → 合并到 positioning 对象
已移除的属性: 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(现在必填 value 属性)AccordionButton → Accordion.ItemTriggerAccordionIcon → Accordion.ItemIndicatorAccordionPanel → Accordion.ItemContent + Accordion.ItemBody
属性变更:
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
组件结构已更改,list 和 panels 现在必须提供必填的 value 属性。
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 属性现在是 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 only shows on screens md and larger.
</Box>Checkbox
已重构为复合组件。单一的 <Checkbox> 现在拆分为显式的部件,以便对结构与样式进行完全控制。
属性变更:
isChecked → checkedisDisabled → disabledisInvalid → invalidisReadOnly → readOnlyisIndeterminate → checked="indeterminate"onChange → onCheckedChangecolorScheme → colorPaletteicon → 渲染为 Checkbox.Control 的 childreniconColor → 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 === true)}
colorPalette="blue"
>
<Checkbox.HiddenInput />
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Label>Accept terms</Checkbox.Label>
</Checkbox.Root>
)Radio Group
已重构为复合组件。Radio 现在是 RadioGroup.Item,带有显式的子组件: ItemHiddenInput、ItemIndicator、 ItemText。
组件重命名:
RadioGroup → RadioGroup.RootRadio → RadioGroup.Item(带必填子组件)
RadioGroup 属性变更:
onChange → onValueChange(接收 { value } 对象)colorScheme → colorPalette
Radio 属性变更:
isDisabled → disabledisInvalid、isChecked、defaultChecked → 已移除(由 Root 控制)colorScheme → 已从 item 移除(请在 Root 上设置 colorPalette)inputProps → 展开到 RadioGroup.ItemHiddenInput
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 属性汇总
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 属性汇总
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 属性汇总
isChecked → checkedisDisabled → disabledisInvalid → invalidisIndeterminate → checked="indeterminate"onChange → onCheckedChangecolorScheme → colorPaletteiconColor → Checkbox.Indicator 上的 coloriconSize → Checkbox.Indicator 上的 boxSizeisFocusable → 已移除
Modal 到 Dialog 属性汇总
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 属性汇总
spacing → gapdivider → separator其他属性保持不变
// 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>