DEV Community

Harsh Mangalam
Harsh Mangalam

Posted on

🧩 Why Dialog Doesn’t Work When Wrapped with Tooltip in Radix / ShadCN UI (And How to Fix It)

When working with Radix UI (used under the hood in shadcn/ui), things are usually smooth — composable, accessible, and beautifully engineered.

But one day, I wrapped a DialogTrigger with a Tooltip and…

Nothing happened.

No errors. No warnings. Just a button that didn’t open the dialog. 🤯

Turns out, there's a common but sneaky mistake that trips up many devs — and it’s all about the order in which you nest your components.


❌ The Broken Version

This is what I initially wrote — trying to wrap my dialog trigger in a tooltip:

<Dialog open={open} onOpenChange={setOpen}>
  <DialogTrigger asChild>
    <Tooltip>
      <TooltipTrigger asChild>
        <Button size="icon" variant="outline">
          <EditIcon />
        </Button>
      </TooltipTrigger>
      <TooltipContent>Edit podcast</TooltipContent>
    </Tooltip>
  </DialogTrigger>
  <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
    {/* Form goes here */}
  </DialogContent>
</Dialog>
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks totally fine.

But the dialog wouldn’t open when clicking the button.
The tooltip shows, but the dialog never appears.

🤔 What’s Going Wrong?
Let’s break this down.

Both DialogTrigger and TooltipTrigger from Radix use the asChild prop, which means:

They don’t render any DOM element themselves.

Instead, they pass event handlers like onClick to their child — which must be a real DOM element, like a .

In the broken example, DialogTrigger wraps Tooltip, which is not a DOM element — it's just a React component.
So the click event never reaches a usable target for Dialog.

âś… The Working Version
Here’s the corrected nesting:

<Dialog open={open} onOpenChange={setOpen}>
  <Tooltip>
    <TooltipTrigger asChild>
      <DialogTrigger asChild>
        <Button size="icon" variant="outline">
          <EditIcon />
        </Button>
      </DialogTrigger>
    </TooltipTrigger>
    <TooltipContent>Edit podcast</TooltipContent>
  </Tooltip>
  <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
    {/* Form goes here */}
  </DialogContent>
</Dialog>


Enter fullscreen mode Exit fullscreen mode

Why does this work?

  • TooltipTrigger passes props down to DialogTrigger
  • DialogTrigger passes props down to the final Button
  • The Button becomes the single, shared DOM element that receives both behaviors

đź§  Rule of Thumb

Always place the outermost wrapper (like Tooltip) outside, and the final DOM element (like Button) inside both DialogTrigger and TooltipTrigger, using asChild.

Top comments (1)

Collapse
 
porobertdev profile image
Robert P. • Edited

Thanks!

In my case, I had to remove asChild. Both examples below work, but the first one has better accessibility score. I wanted to have a tooltip over the tabs.

        <TabsList className="mb-8 flex-col justify-center !items-start gap-6 bg-white h-max">
          {tabItems.map(({ value, label, icon }) => (
            <TabsTrigger
              key={value}
              className="p-2 w-10 h-10 rounded-full hover:bg-primary hover:text-accent transition-all duration-200 data-[state=active]:bg-primary! data-[state=active]:text-accent!"
              value={value}
            >
              <Tooltip>
                <TooltipTrigger>{icon}</TooltipTrigger>
                <TooltipContent>
                  <p>{label}</p>
                </TooltipContent>
              </Tooltip>
            </TabsTrigger>
          ))}
        </TabsList>
Enter fullscreen mode Exit fullscreen mode

Example2:

        <TabsList className="mb-8 flex-col justify-center !items-start gap-6 bg-white h-max">
          {tabItems.map(({ value, label, icon }) => (
            <Tooltip key={value}>
              <TooltipTrigger>
                <TabsTrigger
                  className="p-2 w-10 h-10 rounded-full hover:bg-primary hover:text-accent transition-all duration-200 data-[state=active]:bg-primary! data-[state=active]:text-accent!"
                  value={value}
                >
                  {icon}
                </TabsTrigger>
              </TooltipTrigger>
              <TooltipContent>
                <p>{label}</p>
              </TooltipContent>
            </Tooltip>
          ))}
        </TabsList>
Enter fullscreen mode Exit fullscreen mode