Skip to Content
DocsResourcesShowcase

Migration to v3

How to migrate to Chakra UI v3.x from v2.x

warning
This guide is a work in progress and will be updated from time to time. If you find any point we missed, feel free to open a PR to update this guide.

The minimum node version required is Node.20.x

1

Remove the unused packages: @emotion/styled and framer-motion. These packages are no longer required in Chakra UI.

npm uninstall @emotion/styled framer-motion

Next, install component snippets using the CLI snippets. Snippets that provide pre-built compositions of Chakra components to save you time and put you in charge.

npx @chakra-ui/cli@next snippet add
2

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

.

3

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 value={defaultSystem}>
    <Component />
  </Provider>
)

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

  • Performance: Improved reconciliation performance by 4x and re-render performance by 1.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 as prop typigns in favor of using the asChild prop. This pattern was inspired by Radix Primitives and Ark UI.

  • ColorModeProvider and useColorMode have been removed in favor of next-themes
  • LightMode, DarkMode and ColorModeScript components have been removed
  • useColorModeValue has been removed in favor of useTheme from next-themes
note
We provide snippets for color mode via the CLI to help you set up color mode quickly using next-themes

We removed the hooks package in favor of using dedicated, robust libraries like react-use and usehooks-ts

We removed the styleConfig and multiStyleConfig concept in favor of recipes and slot recipes. This pattern was inspired by Panda CSS.

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 isExternal asChild>
  <NextLink />
</Link>

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%)
})

Due to the simplification of the as prop, we no longer provide a custom forwardRef. Prefer to use forwardRef from React directly.

Removed @chakra-ui/icons package. Prefer to use lucide-react or react-icons instead.

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 preview

  • Wrap: Replace with HStack and add wrap=wrap to it.
  • WrapItem: Replace with Flex and add align=flex-start to it.
  • StackItem: You don't need this anymore. Use Box instead.
  • FocusLock: We no longer ship a focus lock component. Use react-focus-lock instead.

Changed naming convention for boolean properties from is<X> to <x>

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

The colorScheme prop has been changed to colorPalette

Before

  • You could only use colorScheme in a component's theme
  • colorScheme clashes with the native colorScheme prop in HTML elements
<Button colorScheme="blue">Click me</Button>

After

  • You can now use colorPalette anywhere
<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>

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

  • Default color palette is now gray for 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" },
      },
    }

Changed the naming convention for some style props

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

We removed the apply prop in favor of textStyle or layerStyles

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

  • Remove max prop in favor of userland control
  • Remove excess label part
  • Move image related props to Avatar.Image component
  • Move fallback icon to Avatar.Fallback component
  • Move name prop to Avatar.Fallback component

We changed the props for the Wrap component to be more explicit and easier to understand.

  • Changed spacing to gap
  • Changed spacingX to rowGap
  • Changed spacingY to columnGap
  • Removed shouldWrapChildren in favor of using the WrapItem component explicitly

  • Remove appendToParentPortal prop in favor of using the containerRef
  • Remove PortalManager component

  • Changed spacing to gap
  • Removed StackItem in favor of using the Box component directly

  • Rename Collapse to Collapsible namespace
  • Rename in to open
  • animateOpacity has been removed, use keyframes animations expand-height and collapse-height instead

Before:

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

After:

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

  • Now renders a native img without any fallback
  • Remove fallbackSrc due to the SSR issues it causes
  • Remove useImage hook
  • Remove Img in favor of using the Image component directly

  • Changed value, defaultValue and onChange to use string[] instead of string
  • Add new PinInput.Control and PinInput.Label component parts
  • PinInput.Root now renders a div element by default. Consider combining with Stack or Group for better layout control

  • Rename NumberInputStepper to NumberInput.Control
  • Rename NumberInputStepperIncrement to NumberInput.IncrementTrigger
  • Rename NumberInputStepperDecrement to NumberInput.DecrementTrigger
  • Remove focusBorderColor and errorBorderColor, consider setting the --focus-color and --error-color css variables instead

Before:

<NumberInput>
  <NumberInputField />
  <NumberInputStepper>
    <NumberIncrementStepper />
    <NumberDecrementStepper />
  </NumberInputStepper>
</NumberInput>

After:

<NumberInput.Root>
  <NumberInput.Field />
  <NumberInput.Control>
    <NumberInput.IncrementTrigger />
    <NumberInput.DecrementTrigger />
  </NumberInput.Control>
</NumberInput.Root>

  • Rename to Separator
  • Switch to div element for better layout control
  • Simplify component to rely on borderTopWidth and borderInlineStartWidth
  • To change the thickness reliably, set the --divider-border-width css variable

  • Removed invalid prop in favor of wrapping the component in a Field component. This allows for adding a label, error text and asterisk easily.

Before:

<Input invalid />

After:

<Field invalid label="Email" errorText="This field is required">
  <Input />
</Field>
  • Removed external prop in favor of explicitly setting the target and rel props

Before:

<Link external>Click me</Link>

After:

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

Previous

Installation

Next

CLI