Migration to v3
How to migrate to Chakra UI v3.x from v2.x
Steps
The minimum node version required is Node.20.x
Update Packages
Remove the unused packages: @emotion/styled and framer-motion. These
packages are no longer required in Chakra UI.
npm uninstall @emotion/styled framer-motionInstall updated versions of the packages: @chakra-ui/react and
@emotion/react.
npm install @chakra-ui/react@latest @emotion/react@latestNext, install component snippets using the CLI snippets. Snippets provide pre-built compositions of Chakra components to save you time and put you in charge.
npx @chakra-ui/cli snippet addRefactor Custom Theme
Move your custom theme to a dedicated theme.js or theme.ts file. Use
createSystem and defaultConfig to configure your theme.
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` },
},
},
},
})All token values need to be wrapped in an object with a value key. Learn more about tokens here.
Update ChakraProvider
Update the ChakraProvider import from @chakra-ui/react to the one from the
snippets. Next, rename the theme prop to value to match the new system-based
theming approach.
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>
)
}If you have a custom theme, replace defaultSystem with the custom system
The Provider component compose the ChakraProvider from Chakra and
ThemeProvider from next-themes
Improvements
-
Performance: Improved reconciliation performance by
4xand re-render performance by1.6x -
Namespaced imports: Import components using the dot notation for more concise imports
import { Accordion } from "@chakra-ui/react" const Demo = () => { return ( <Accordion.Root> <Accordion.Item> <Accordion.ItemTrigger /> <Accordion.ItemContent /> </Accordion.Item> </Accordion.Root> ) } -
TypeScript: Improved IntelliSense and type inference for style props and tokens.
-
Polymorphism: Loosened the
asprop typings in favor of using theasChildprop. This pattern was inspired by Radix Primitives and Ark UI.
Removed Features
Color Mode
ColorModeProvideranduseColorModehave been removed in favor ofnext-themesLightMode,DarkModeandColorModeScriptcomponents have been removed. You now have to useclassName="light"orclassName="dark"to force themes.useColorModeValuehas been removed in favor ofuseThemefromnext-themes
next-themesHooks
We removed the hooks package in favor of using dedicated, robust libraries like
react-use and usehooks-ts
The only hooks we ship now are useBreakpointValue, useCallbackRef,
useDisclosure, useControllableState and useMediaQuery.
Style Config
We removed the styleConfig and multiStyleConfig concept in favor of recipes
and slot recipes. This pattern was inspired by Panda CSS.
Next.js package
We've removed the @chakra-ui/next-js package in favor of using the asChild
prop for better flexibility.
To style the Next.js image component, use the asChild prop on the Box
component.
<Box asChild>
<NextImage />
</Box>To style the Next.js link component, use the asChild prop on the Link
component
<Link isExternal asChild>
<NextLink />
</Link>Theme Tools
We've removed this package in favor using CSS color mix.
Before
We used JS to resolve the colors and then apply the transparency
defineStyle({
bg: transparentize("blue.200", 0.16)(theme),
// -> rgba(0, 0, 255, 0.16)
})After
We now use CSS color-mix
defineStyle({
bg: "blue.200/16",
// -> color-mix(in srgb, var(--chakra-colors-200), transparent 16%)
})forwardRef
Due to the simplification of the as prop, we no longer provide a custom
forwardRef. Prefer to use forwardRef from React directly.
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
Removed @chakra-ui/icons package. Prefer to use lucide-react or
react-icons instead.
Storybook Addon
We're removed the storybook addon in favor of using @storybook/addon-themes
and withThemeByClassName helper.
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 previewRemoved Components
- StackItem: You don't need this anymore. Use
Boxinstead. - FocusLock: We no longer ship a focus lock component. Install and use
react-focus-lockdirectly. - AlertDialog
- Replace with the
Dialogcomponent and setrole=alertdialog - Set
leastDestructiveRefprop to theinitialFocusElto theDialog.Rootcomponent
- Replace with the
CircularProgress
- Renamed to
ProgressCircleand now uses compound components isIndeterminatebecomesvalue={null}thicknessprop becomes--thicknessCSS variablecolorprop becomesstrokeprop onProgressCircle.Range
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>For indeterminate progress:
<ProgressCircle.Root value={null}>
<ProgressCircle.Circle>
<ProgressCircle.Track />
<ProgressCircle.Range />
</ProgressCircle.Circle>
</ProgressCircle.Root>StackDivider
- No longer available as a separate component
- Use explicit
Stack.Separatorcomponents between stack items
Before:
<VStack divider={<StackDivider borderColor="gray.200" />} spacing={4}>
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</VStack>After:
<VStack gap={4}>
<Box>Item 1</Box>
<Stack.Separator borderColor="gray.200" />
<Box>Item 2</Box>
<Stack.Separator borderColor="gray.200" />
<Box>Item 3</Box>
</VStack>Prop Changes
Boolean Props
Changed naming convention for boolean properties from is<X> to <x>
isOpen->opendefaultIsOpen->defaultOpenisDisabled->disabledisInvalid->invalidisRequired->required
ColorScheme Prop
The colorScheme prop has been changed to colorPalette
Before
- You could only use
colorSchemein a component's theme colorSchemeclashes with the nativecolorSchemeprop in HTML elements
<Button colorScheme="blue">Click me</Button>After
- You can now use
colorPaletteanywhere
<Button colorPalette="blue">Click me</Button>Usage in any component, you can do something like:
<Box colorPalette="red">
<Box bg="colorPalette.400">Some box</Box>
<Text color="colorPalette.600">Some text</Text>
</Box>If you are using custom colors, you must define two things to make
colorPalette work:
- tokens: For the 50-950 color palette
- semanticTokens: For the
solid,contrast,fg,muted,subtle,emphasized, andfocusRingcolor keys
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}" },
},
},
},
},
})Read more about it here.
Gradient Props
Gradient style prop simplified to gradient and gradientFrom and gradientTo
props. This reduces the runtime performance cost of parsing the gradient string,
and allows for better type inference.
Before
<Box bgGradient="linear(to-r, red.200, pink.500)" />After
<Box bgGradient="to-r" gradientFrom="red.200" gradientTo="pink.500" />Color Palette
-
Default color palette is now
grayfor all components but you can configure this in your theme. -
Default theme color palette size has been increased to 11 shades to allow more color variations.
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
Changed the naming convention for some style props
noOfLines->lineClamptruncated->truncate_activeLink->_currentPage_activeStep->_currentStep_mediaDark->_osDark_mediaLight->_osLight
Examples:
// 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>We removed the apply prop in favor of textStyle or layerStyles
Nested Styles
We have changed the way you write nested styles in Chakra UI components.
Before
Write nested styles using the sx or __css prop, and you sometimes don't get
auto-completion for nested styles.
<Box
sx={{
svg: { color: "red.500" },
}}
/>After
Write nested styles using the css prop. All nested selectors require the
use of the ampersand & prefix
<Box
css={{
"& svg": { color: "red.500" },
}}
/>This was done for two reasons:
- Faster style processing: Before we had to check if a style key is a style prop or a selector which is quite expensive overall.
- Better typings: This makes it easier to type nested style props are strongly typed
Component Changes
ChakraProvider
-
Removed
themeprop in favor of passing thesystemprop instead. Import thedefaultSystemmodule instead oftheme -
Removed
resetCssprop in favor of passingpreflight: falseto thecreateSystemfunction
Before
<ChakraProvider resetCss={false}>
<Component />
</ChakraProvider>After
const system = createSystem(defaultConfig, { preflight: false })
<Provider value={system}>
<Component />
</Provider>- Removed support for configuring toast options. Pass it to the
createToasterfunction incomponents/ui/toaster.tsxfile instead.
Modal
- Renamed to
Dialog - Remove
isCenteredprop in favor of using theplacement=centerprop - Removed
isOpenandonCloseprops in favor of using theopenandonOpenChangeprops
Avatar
- Remove
maxprop in favor of userland control - Remove excess label part
- Move image related props to
Avatar.Imagecomponent - Move fallback icon to
Avatar.Fallbackcomponent - Move
nameprop toAvatar.Fallbackcomponent
Portal
- Remove
appendToParentPortalprop in favor of using thecontainerRef - Remove
PortalManagercomponent
Progress
- Now uses compound components with
Progress.Root,Progress.Track, andProgress.Range hasStripeprop renamed tostripedisAnimatedprop renamed toanimatedcolorSchemeprop renamed tocolorPalette
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
- Changed
spacingtogap - Removed
StackItemin favor of using theBoxcomponent directly
Select
Now called NativeSelect and exposes all parts now.
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>Changing the icon
Before:
<Select icon={<MdArrowDropDown />} placeholder="Woohoo! A new icon" />After:
<NativeSelect.Indicator>
<MdArrowDropDown />
</NativeSelect.Indicator>Collapse
- Rename
CollapsetoCollapsiblenamespace - Rename
intoopen animateOpacityhas been removed, use keyframes animationsexpand-heightandcollapse-heightinstead
Before
<Collapse in={isOpen} animateOpacity>
Some content
</Collapse>After
<Collapsible.Root open={isOpen}>
<Collapsible.Content>Some content</Collapsible.Content>
</Collapsible.Root>Image
- Now renders a native
imgwithout any fallback - Remove
fallbackSrcdue to the SSR issues it causes - Remove
useImagehook - Remove
Imgin favor of using theImagecomponent directly
PinInput
- Changed
value,defaultValueto usestring[]instead ofstring onChangeprop is now calledonValueChange- Add new
PinInput.ControlandPinInput.Labelcomponent parts PinInput.Rootnow renders adivelement by default. Consider combining withStackorGroupfor better layout controlonCompleteprop is now calledonValueComplete
NumberInput
- Rename
NumberInputSteppertoNumberInput.Control - Rename
NumberInputStepperIncrementtoNumberInput.IncrementTrigger - Rename
NumberInputStepperDecrementtoNumberInput.DecrementTrigger onChangeprop is now calledonValueChange- Remove
focusBorderColoranderrorBorderColor, consider setting the--focus-colorand--error-colorcss variables instead onInvalidprop is now calledonValueInvalidparseandformatprops removed in favor offormatOptionsprop
Before
<NumberInput>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>After
<NumberInput.Root>
<NumberInput.Input />
<NumberInput.Control>
<NumberInput.IncrementTrigger />
<NumberInput.DecrementTrigger />
</NumberInput.Control>
</NumberInput.Root>Divider
- Rename to
Separator - Switch to
divelement for better layout control - Simplify component to rely on
borderTopWidthandborderInlineStartWidth - To change the thickness reliably, set the
--divider-border-widthcss variable
Input, Select, Textarea
- Removed
invalidprop in favor of wrapping the component in aFieldcomponent. This allows for adding a label, error text and asterisk easily.
Before
<Input invalid />After
<Field.Root invalid>
<Field.Label>Email</Field.Label>
<Input />
<Field.ErrorText>This field is required</Field.ErrorText>
</Field.Root>Link
- Removed
isExternalprop in favor of explicitly setting thetargetandrelprops
Before
<Link isExternal>Click me</Link>After
<Link target="_blank" rel="noopener noreferrer">
Click me
</Link>Button
- Removed
isActivein favor of passingdata-active
Before
<Button isActive>Click me</Button>After
<Button data-active>Click me</Button>IconButton
- Removed
iconprop in favor of rendering thechildrenprop directly - Removed
isRoundedin favor of using theborderRadius=fullprop
Spinner
- Change the
thicknessprop toborderWidth - Change the
speedprop toanimationDuration
Before
<Spinner thickness="2px" speed="0.5s" />After
<Spinner borderWidth="2px" animationDuration="0.5s" />Dialog, Drawer
isOpenandonChangeprops have been removed in favor ofopenandonOpenChangepropsblockScrollOnMountis nowpreventScrollcloseOnEscis nowcloseOnEscapecloseOnOverlayClickis nowcloseOnInteractOutsideinitialFocusRefis now aninitialFocusElfunction that returns the elementfinalFocusRefis now anfinalFocusElfunction that returns the element
Editable
finalFocusRefis nowfinalFocusElfunction that returns the elementisDisabledis nowdisabledonSubmitis nowonValueCommitonCancelis nowonValueRevertonChangeis nowonValueChangestartWithEditViewis nowdefaultEdit- Replace
submitOnBlurwithsubmitMode
FormControl
- Replace
FormControlwith theFieldcomponent. - Replace
FormErrorMessagewith theField.ErrorTextcomponent.
Before:
<FormControl>
<Input />
<FormErrorMessage>This field is required</FormErrorMessage>
</FormControl>After:
<Field.Root>
<Input />
<Field.ErrorText>This field is required</Field.ErrorText>
</Field.Root>Collapse
Replace with the Collapsible component.
Before:
<Collapse in={isOpen} animateOpacity>
Some content
</Collapse>After:
<Collapsible.Root open={isOpen}>
<Collapsible.Content>Some content</Collapsible.Content>
</Collapsible.Root>Slider
onChangeprop is now calledonValueChangeonChangeEndprop is now calledonValueChangeEndonChangeStartprop is now removedisReversedprop is now removed
RangeSlider
Can now be used as a single slider by passing an array of values
Before:
<RangeSlider defaultValue={[10, 30]}>
<RangeSliderTrack>
<RangeSliderFilledTrack />
</RangeSliderTrack>
<RangeSliderThumb index={0} />
<RangeSliderThumb index={1} />
</RangeSlider>After:
<Slider.Root defaultValue={[10, 30]}>
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumbs />
</Slider.Control>
</Slider.Root>Table
TableContaineris nowTable.ScrollAreaTd(now calledTable.ColumnHeader)isNumericis nowtextAlign="end"
The compound component have been renamed slightly.
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 and TagRightIcon are now Tag.StartElement and 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>TagCloseButtonis nowTag.CloseTrigger
Before:
<Tag>
<TagLabel>Green</TagLabel>
<TagCloseButton />
</Tag>After:
<Tag.Root>
<Tag.Label>Green</Tag.Label>
<Tag.CloseTrigger />
</Tag.Root>Alert
AlertIconis nowAlert.Indicator
Before:
<Alert>
<AlertIcon />
<AlertTitle>Your browser is outdated!</AlertTitle>
<AlertDescription>Your Chakra experience may be degraded.</AlertDescription>
</Alert>After:
<Alert.Root status="error">
<Alert.Indicator />
<Alert.Content>
<Alert.Title>Invalid Fields</Alert.Title>
<Alert.Description>
Your form has some errors. Please fix them and try again.
</Alert.Description>
</Alert.Content>
</Alert.Root>- Removed
addRoleprop in favor ofroleprop.
Skeleton
startColorandendColorprops now use CSS variables
Before:
<Skeleton startColor="pink.500" endColor="orange.500" />After:
<Skeleton
css={{
"--start-color": "colors.pink.500",
"--end-color": "colors.orange.500",
}}
/>isLoadedprop is nowloading
Before:
<Skeleton isLoaded>
<span>Chakra ui is cool</span>
</Skeleton>After:
<Skeleton loading={false}>
<span>Chakra ui is cool</span>
</Skeleton>Menu
- Now uses compound components everywhere
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>- Accesing internal state is now done via
Menu.Contextno longer 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("Kagebunshin")}>
Create a Copy
</Menu.Item>
</Menu.Content>
</Menu.Positioner>
</Portal>
</Menu.Root>-
isLazyprop onMenuis split intolazyMountandunmountOnExitonMenu.Root -
MenuOptionGroupis now split intoMenu.RadioItemGroupandMenu.CheckboxItemGroupto handle the states separately.
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
-
closeOnEscnow renamed tocloseOnEscape -
closeOnMouseDownis nowcloseOnPointerDown -
placement,gutter,offsetandarrowonTooltipis now included aspositioningprop onTooltip.Root
Before:
<Tooltip placement="top" />After:
<Tooltip.Root positioning={{ placement: "top" }} />Accordion
- These props have been changed:
allowMultiple->multipleallowToggle->collapsibleindex->valuedefaultIndex->defaultValue
Before:
<Accordion allowMultiple index={[0]} onChange={() => {}} />After:
<Accordion multiple value={["0"]} onValueChange={() => {}} />AccordionButtonis nowAccordion.TriggerAccordionIconis nowAccordion.ItemIndicator
Before:
<AccordionButton>Section 1 title</AccordionButton>After:
<Accordion.Trigger>Section 1 title</Accordion.Trigger>Tabs
- Component structure has changed and
valueprop is now required on list and panels.
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,indexandonChangeis nowdefaultValue,valueandonValueChangerespectively
Before:
<Tabs defaultIndex={0} index={0} onChange={(index) => {}} />After:
<Tabs defaultValue={0} value={0} onValueChange={({ value }) => {}} />isLazyprop onTabsis nowlazyMountandunmountOnExitonTabs.Root
Before:
<Tabs isLazy />After:
<Tabs.Root lazyMount unmountOnExit />Show and Hide
ShowandHidecomponents are removed in favor ofhideFromandhideBelow
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
- Refactored to use compound components
Before:
<Checkbox defaultChecked>Checkbox</Checkbox>After:
<Checkbox.Root defaultChecked>
<Checkbox.HiddenInput />
<Checkbox.Control>
<Checkbox.Indicator />
</Checkbox.Control>
<Checkbox.Label>Checkbox</Checkbox.Label>
</Checkbox.Root>Radio Group
- Refactored to use compound components
Before:
<RadioGroup defaultValue="2">
<Radio value="1">Radio</Radio>
<Radio value="2">Radio</Radio>
</RadioGroup>After:
<RadioGroup.Root defaultValue="2">
<RadioGroup.Item value="1">
<RadioGroup.ItemHiddenInput />
<RadioGroup.ItemIndicator />
<RadioGroup.ItemText />
</RadioGroup.Item>
</RadioGroup.Root>Button Props
isActive→data-activeattributeisDisabled→disabledisLoading→loadingleftIconandrightIcon→ passed as childreniconSpacing→ removed (use gap in flex layout)colorScheme→colorPalette
Example:
// 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→ use CSS variableserrorBorderColor→ use CSS variables
Example:
// 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→indeterminate(on Indicator)colorScheme→colorPaletteiconColor→ removed (use CSS)iconSize→ removed (use CSS)spacing→ removed (use gap)
Example:
// Before
<Checkbox
isChecked={true}
isDisabled={false}
isInvalid={true}
isIndeterminate={true}
colorScheme="blue"
>
Accept terms
</Checkbox>
// After
<Checkbox.Root
checked={true}
disabled={false}
invalid={true}
colorPalette="blue"
>
<Checkbox.Control>
<Checkbox.Indicator indeterminate={true} />
</Checkbox.Control>
<Checkbox.Label>Accept terms</Checkbox.Label>
</Checkbox.Root>Modal to Dialog Props
isOpen→openonClose→onOpenChange(different signature)isCentered→placement="center"scrollBehavior→ samemotionPreset→ updated values (e.g.,slideInBottom→slide-in-bottom)closeOnOverlayClick→closeOnInteractOutsidecloseOnEsc→closeOnEscapeblockScrollOnMount→preventScrollreturnFocusOnClose→restoreFocusinitialFocusRef→initialFocusEl(function)finalFocusRef→finalFocusEl(function)
Example:
// Before
<Modal
isOpen={isOpen}
onClose={onClose}
isCentered={true}
closeOnOverlayClick={true}
initialFocusRef={initialRef}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Title</ModalHeader>
<ModalBody>Content</ModalBody>
</ModalContent>
</Modal>
// After
<Dialog.Root
open={isOpen}
onOpenChange={(e) => !e.open && onClose()}
placement="center"
closeOnInteractOutside={true}
initialFocusEl={() => initialRef.current}
>
<Dialog.Backdrop />
<Dialog.Positioner>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Title</Dialog.Title>
</Dialog.Header>
<Dialog.Body>Content</Dialog.Body>
</Dialog.Content>
</Dialog.Positioner>
</Dialog.Root>Stack Props
spacing→gapdivider→separator- Other props remain the same
Example:
// Before
<Stack
spacing="4"
divider={<StackDivider />}
>
<Box>Item 1</Box>
<Box>Item 2</Box>
</Stack>
// After
<Stack
gap="4"
separator={<Stack.Separator />}
>
<Box>Item 1</Box>
<Box>Item 2</Box>
</Stack>