Table of Contents#
- Understanding the Issue: What’s Happening?
- Common Causes of Mispositioned Tooltips
- Step-by-Step Solutions to Fix the Tooltip
- Advanced Troubleshooting
- Conclusion
- References
1. Understanding the Issue: What’s Happening?#
When a Chakra UI Tooltip appears in the top-left corner, it’s almost always due to a failure in the positioning logic. Chakra UI’s Tooltip component relies on Popper.js (a popular positioning library) under the hood to calculate the tooltip’s position relative to its target element. Popper needs a valid "reference element" (the target the tooltip is attached to) to compute coordinates. If Popper can’t find or access this reference element, it defaults to positioning the tooltip at the origin point (0, 0) of the viewport—hence the top-left corner.
Example Scenario: You wrap a button with a Tooltip, but when you hover over the button, the tooltip appears in the top-left corner instead of above/below/next to the button.
2. Common Causes of Mispositioned Tooltips#
Let’s break down the most likely reasons Popper fails to position the tooltip correctly:
2.1 Missing label Prop#
The label prop is required for the Tooltip component—it defines the content displayed in the tooltip. While omitting label won’t prevent the tooltip from rendering (in some Chakra versions), it can cause the positioning logic to fail. Without content, Popper may not initialize properly, leading to default (0, 0) positioning.
Broken Example:
// ❌ Missing label prop
<Tooltip>
<Button>Hover Me</Button>
</Tooltip>2.2 Incorrect Target Element Structure (Multiple Children or Fragments)#
The Tooltip component expects a single child element to act as the target/reference. If you pass multiple children, a React fragment, or a non-element (e.g., a string), Chakra may struggle to identify the reference element. Popper then falls back to (0, 0) because it can’t find a valid bounding box to position against.
Broken Example:
// ❌ Multiple children passed to Tooltip
<Tooltip label="Click to submit">
<span>Submit</span>
<Button>Button</Button> {/* Tooltip can't decide which child is the reference */}
</Tooltip>
// ❌ Using a fragment
<Tooltip label="Hello">
<>
<Button>Hover Me</Button>
</>
</Tooltip>2.3 Target Element Not Rendered or Unavailable#
If the target element (the child of Tooltip) is conditionally rendered and hasn’t mounted yet when the tooltip tries to position itself, Popper will have no reference element. This often happens with components that render after an API call or state change.
Broken Example:
// ❌ Target is conditionally rendered and not yet mounted
<Tooltip label="User Profile">
{userLoaded && <Button>View Profile</Button>} {/* Tooltip mounts before userLoaded is true */}
</Tooltip>2.4 Parent Element with overflow: hidden or transform#
Popper calculates positioning relative to the viewport by default, but if the tooltip’s parent element has overflow: hidden, the tooltip may be clipped. However, a less obvious issue is parent elements with transform: translateZ(0) or other transform properties. These create a new "stacking context", causing Popper to miscalculate the reference element’s position.
2.5 State Management Issues (isOpen Timing)#
If you manually control the tooltip’s visibility with the isOpen prop (instead of relying on default hover/focus behavior), timing is critical. If isOpen is set to true before the target element mounts, Popper can’t find the reference, leading to (0, 0) positioning.
Broken Example:
// ❌ isOpen is true before the target mounts
const [isOpen, setIsOpen] = useState(true);
return (
<Tooltip label="Tooltip" isOpen={isOpen} onClose={() => setIsOpen(false)}>
<Button>Hover Me</Button> {/* Tooltip tries to position before Button mounts */}
</Tooltip>
);3. Step-by-Step Solutions to Fix the Tooltip#
Now that we’ve identified the causes, let’s fix them with actionable solutions.
3.1 Ensure the label Prop Is Set#
Always include the label prop—it’s required for content and proper initialization.
Fixed Example:
// ✅ label prop is included
<Tooltip label="Click to save changes">
<Button>Save</Button>
</Tooltip>3.2 Wrap Target in a Single Element#
The Tooltip must have a single child element (not a fragment or multiple elements). If you need multiple elements, wrap them in a container like Box or div.
Fixed Example:
// ✅ Single child (Box wraps multiple elements)
<Tooltip label="Submit form">
<Box> {/* Wrapper acts as the reference element */}
<span>Submit</span>
<Button>Go</Button>
</Box>
</Tooltip>3.3 Avoid Conditional Rendering of the Target Before the Tooltip#
Ensure the Tooltip itself is conditionally rendered only after the target element is ready. This way, the target and tooltip mount together, and Popper can find the reference.
Fixed Example:
// ✅ Tooltip is conditionally rendered with the target
{userLoaded && (
<Tooltip label="User Profile">
<Button>View Profile</Button>
</Tooltip>
)}3.4 Adjust Parent Element CSS#
If a parent element has overflow: hidden or transform, modify the CSS to avoid clipping or stacking context issues:
- Remove
overflow: hiddenfrom the parent (or useoverflow: visibleif safe). - Avoid unnecessary
transformproperties on parent elements. Iftransformis required, usePopper’smodifiersto adjust positioning (see Advanced Troubleshooting).
3.5 Properly Manage isOpen State#
If using isOpen, ensure it’s set to true only after the target element mounts. Use useEffect to delay setting isOpen until the target is available.
Fixed Example:
// ✅ isOpen is set after the target mounts
const [isOpen, setIsOpen] = useState(false);
const targetRef = useRef(null);
useEffect(() => {
// Wait for the target to mount before opening the tooltip
if (targetRef.current) {
setIsOpen(true);
}
}, []);
return (
<Tooltip
label="Delayed Tooltip"
isOpen={isOpen}
onClose={() => setIsOpen(false)}
>
<Button ref={targetRef}>Hover Me</Button>
</Tooltip>
);4. Advanced Troubleshooting#
If the basic fixes above don’t work, try these advanced strategies:
4.1 Use Popper Directly#
Chakra UI’s Tooltip is a wrapper around the Popper component. If the Tooltip abstraction is causing issues, use Popper directly for more control over positioning:
import { Popper, Box, Button } from '@chakra-ui/react';
function CustomTooltip() {
const [isOpen, setIsOpen] = useState(false);
const targetRef = useRef(null);
return (
<>
<Button
ref={targetRef}
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
>
Hover Me
</Button>
{isOpen && (
<Popper placement="top" referenceElement={targetRef.current}>
{({ placement, transitionProps }) => (
<Box
bg="black"
color="white"
p={2}
borderRadius="md"
{...transitionProps}
>
Custom Tooltip Content
</Box>
)}
</Popper>
)}
</>
);
}4.2 Inspect the DOM and Tooltip Positioning#
Use your browser’s DevTools to:
- Check if the tooltip is rendered in a portal (Chakra renders tooltips in a
portaldiv at the end of<body>by default). - Verify the reference element’s bounding box (use the "Elements" tab to inspect the target element’s
getBoundingClientRect()). - Look for inline styles on the tooltip (Popper adds
top/leftstyles dynamically—if these are0px, Popper failed to calculate position).
4.3 Update Chakra UI#
Older versions of Chakra UI had bugs related to Tooltip positioning (e.g., this GitHub issue). Ensure you’re using the latest version:
npm install @chakra-ui/react@latest
# or
yarn add @chakra-ui/react@latest5. Conclusion#
Chakra UI’s Tooltip appearing in the top-left corner is a common but solvable issue. The root cause is almost always a failure in Popper.js’s positioning logic, typically due to:
- A missing
labelprop. - An invalid or unavailable reference element (e.g., multiple children, conditional rendering).
- Parent element CSS interfering with positioning.
- Prematurely setting
isOpenbefore the target mounts.
By ensuring the target element is a single, rendered element with a valid reference, and managing state/timing correctly, you can resolve this issue and ensure tooltips position reliably.