packages/codemod/docs/TOOLTIP_MIGRATION.md
The Tooltip component in Chakra UI v3 is now a snippet component that you
copy into your project, rather than importing from @chakra-ui/react. This
guide covers the automatic transformations and manual considerations when
migrating.
The tooltip transform automatically handles:
@chakra-ui/react to
@/components/ui/tooltiplabel → content, hasArrow → showArrow,
closeOnEsc → closeOnEscape, closeOnMouseDown → closeOnPointerDownonOpen and onClose into single
onOpenChange handlerplacement, gutter, offset,
arrow, arrowPadding into single positioning object[mainAxis, crossAxis] to object
{ mainAxis, crossAxis }<span> elementmodifiers, motionProps,
portalProps, arrowSize, arrowShadowColorimport { Tooltip } from "@chakra-ui/react"
import { Tooltip } from "@/components/ui/tooltip"
Note: The snippet must be copied to your project first. See the Chakra UI documentation for the snippet code.
When you have multiple Chakra imports, only Tooltip is moved:
Before:
import { Box, Button, Tooltip } from "@chakra-ui/react"
After:
import { Tooltip } from "@/components/ui/tooltip"
import { Box, Button } from "@chakra-ui/react"
The label prop has been renamed to content.
Before:
<Tooltip label="Helpful information">
<button>Hover me</button>
</Tooltip>
After:
<Tooltip content="Helpful information">
<button>Hover me</button>
</Tooltip>
The hasArrow prop has been renamed to showArrow.
Before:
<Tooltip label="Info" hasArrow>
<button>Hover me</button>
</Tooltip>
After:
<Tooltip content="Info" showArrow>
<button>Hover me</button>
</Tooltip>
closeOnEsc → closeOnEscape:
// Before
<Tooltip label="Info" closeOnEsc={false}>
// After
<Tooltip content="Info" closeOnEscape={false}>
closeOnMouseDown → closeOnPointerDown:
// Before
<Tooltip label="Info" closeOnMouseDown>
// After
<Tooltip content="Info" closeOnPointerDown>
The separate onOpen and onClose callbacks are now merged into a single
onOpenChange handler that receives an event object with an open property.
Before:
<Tooltip
label="Info"
onOpen={() => console.log("Tooltip opened")}
onClose={() => console.log("Tooltip closed")}
>
<button>Hover</button>
</Tooltip>
After:
<Tooltip
content="Info"
onOpenChange={(e) => {
if (e.open) {
console.log("Tooltip opened")
} else {
console.log("Tooltip closed")
}
}}
>
<button>Hover</button>
</Tooltip>
Note: The codemod automatically merges both handlers into a single
conditional. If you only had onOpen or onClose, the other branch will be
empty.
Positioning-related props are now grouped into a positioning object.
Props that are grouped:
placementgutteroffset (also transforms array to object)arrowBefore:
<Tooltip label="Info" placement="bottom-end" gutter={16} offset={[10, 5]}>
<button>Click</button>
</Tooltip>
After:
<Tooltip
content="Info"
positioning={{
placement: "bottom-end",
gutter: 16,
offset: {
mainAxis: 10,
crossAxis: 5,
},
}}
>
<button>Click</button>
</Tooltip>
Note: The offset prop is transformed from an array [mainAxis, crossAxis]
to an object { mainAxis, crossAxis } for better clarity.
Arrow-related props are handled differently in v3:
arrowPadding → positioning.arrowPadding:
// Before
<Tooltip label="Info" arrowPadding={8} hasArrow>
// After
<Tooltip content="Info" showArrow positioning={{ arrowPadding: 8 }}>
arrowSize → removed (use CSS instead):
// Before
<Tooltip label="Info" arrowSize={10} hasArrow>
// After (arrowSize removed, use CSS for custom sizing)
<Tooltip content="Info" showArrow>
arrowShadowColor → removed:
// Before
<Tooltip label="Info" arrowShadowColor="red.500" hasArrow>
// After (arrowShadowColor removed)
<Tooltip content="Info" showArrow>
These props are removed in v3 and the codemod will automatically delete them:
modifiers - Use positioning props insteadmotionProps - Animation handled by snippetportalProps - Portal behavior handled by snippetarrowSize - Use CSS variables for custom arrow sizingarrowShadowColor - Use CSS for arrow stylingThe shouldWrapChildren prop is replaced by automatically wrapping children in
a <span> element:
Before:
<Tooltip label="Info" shouldWrapChildren>
Hover text
</Tooltip>
After:
<Tooltip content="Info">
<span>Hover text</span>
</Tooltip>
This also works with JSX children:
Before:
<Tooltip label="Info" shouldWrapChildren>
<button>Click</button>
</Tooltip>
After:
<Tooltip content="Info">
<span>
<button>Click</button>
</span>
</Tooltip>
import { Box, Tooltip } from "@chakra-ui/react"
export default function App() {
return (
<Box>
<Tooltip
label="Click to edit profile"
hasArrow
arrowSize={12}
arrowPadding={8}
closeOnEsc={false}
closeOnMouseDown
placement="right"
gutter={12}
offset={[0, 8]}
onOpen={() => console.log("opened")}
onClose={() => console.log("closed")}
shouldWrapChildren
modifiers={[{ name: "preventOverflow" }]}
motionProps={{ initial: { opacity: 0 } }}
bg="gray.800"
color="white"
openDelay={500}
>
Edit Profile
</Tooltip>
</Box>
)
}
import { Tooltip } from "@/components/ui/tooltip"
import { Box } from "@chakra-ui/react"
export default function App() {
return (
<Box>
<Tooltip
content="Click to edit profile"
showArrow
closeOnEscape={false}
closeOnPointerDown
onOpenChange={(e) => {
if (e.open) {
console.log("opened")
} else {
console.log("closed")
}
}}
bg="gray.800"
color="white"
openDelay={500}
positioning={{
placement: "right",
gutter: 12,
offset: {
mainAxis: 0,
crossAxis: 8,
},
arrowPadding: 8,
}}
>
<span>Edit Profile</span>
</Tooltip>
</Box>
)
}
Note: The codemod automatically:
arrowSize, modifiers, motionProps (deprecated props)<span> (due to shouldWrapChildren)onOpen/onClose into onOpenChangearrowPaddingoffset array to object| v2 Prop | v3 Prop | Notes |
|---|---|---|
label | content | Content to display in tooltip |
hasArrow | showArrow | Show arrow indicator |
closeOnEsc | closeOnEscape | Close on Escape key |
closeOnMouseDown | closeOnPointerDown | Close on pointer down |
| v2 Props | v3 Prop | Notes |
|---|---|---|
onOpen + onClose | onOpenChange | Combined into single handler with e.open condition |
These props are now nested under the positioning object:
| Prop | Type | Description |
|---|---|---|
placement | string | Tooltip placement position |
gutter | number | Distance between tooltip and trigger |
offset | { mainAxis: number, crossAxis: number } | Offset from default position (transformed from array) |
arrow | object | Arrow configuration |
These props work the same in v3:
defaultOpen - Default open stateopen - Controlled open stateopenDelay - Delay before opening (ms)closeDelay - Delay before closing (ms)bg, color, fontSize, etc.Before running the codemod, copy the Tooltip snippet to your project:
# Copy from Chakra UI documentation
# Save to: src/components/ui/tooltip.tsx
npx @chakra-ui/codemod@latest tooltip path/to/files
If you don't use @/ as your path alias, update the imports manually:
// Change from:
import { Tooltip } from '@/components/ui/tooltip'
// To your project's convention:
import { Tooltip } from '~/components/ui/tooltip'
// or
import { Tooltip } from 'components/ui/tooltip'
The codemod groups positioning props automatically, but review the output to ensure correct values:
// Auto-generated positioning object
positioning={{
placement: 'bottom',
gutter: 8,
offset: [0, 10],
}}
Before:
<Tooltip label={isDisabled ? "Not available" : "Click to continue"}>
<button disabled={isDisabled}>Continue</button>
</Tooltip>
After:
<Tooltip content={isDisabled ? "Not available" : "Click to continue"}>
<button disabled={isDisabled}>Continue</button>
</Tooltip>
Before:
<Tooltip
label="Custom tooltip"
bg="purple.500"
color="white"
fontSize="md"
px={4}
py={2}
borderRadius="lg"
>
<button>Hover</button>
</Tooltip>
After:
<Tooltip
content="Custom tooltip"
bg="purple.500"
color="white"
fontSize="md"
px={4}
py={2}
borderRadius="lg"
>
<button>Hover</button>
</Tooltip>
Before:
<Tooltip
label="Info"
hasArrow
arrow={{ size: 12, shadowColor: "gray.300" }}
placement="top"
>
<span>Info</span>
</Tooltip>
After:
<Tooltip
content="Info"
hasArrow
positioning={{
placement: "top",
arrow: { size: 12, shadowColor: "gray.300" },
}}
>
<span>Info</span>
</Tooltip>
The codemod only transforms Tooltip components imported from @chakra-ui/react.
Custom tooltip components are not affected:
import { Tooltip } from "./custom-tooltip"
// This will NOT be transformed
;<Tooltip label="Custom">Content</Tooltip>
Spread props are preserved on the component:
// Before
<Tooltip label="Info" {...tooltipProps}>
// After
<Tooltip content="Info" {...tooltipProps}>
Note: If tooltipProps contains positioning props, you may need to manually
update the object structure.
Problem: Cannot find module '@/components/ui/tooltip'
Solution: Ensure you've copied the Tooltip snippet to your project. Check
that the path alias @/ is configured in your tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
Problem: Tooltip positioning is incorrect after migration.
Solution: Check that positioning props are correctly grouped:
// Correct
positioning={{ placement: 'top', gutter: 8 }}
// Incorrect (will not work)
placement="top"
gutter={8}
Problem: If you have spread props with positioning, you might end up with duplicates.
Solution: Manually merge positioning objects:
// Before auto-fix
<Tooltip
content="Info"
{...props}
positioning={{ placement: 'top' }}
/>
// After manual fix
<Tooltip
content="Info"
{...props}
positioning={{ ...props.positioning, placement: 'top' }}
/>
@/ to your convention)