Skip to content

Popover don't open when combined with Tooltip #2557

Closed

Description

@piszczu4

I tried to make TooltipButton and PopoverButton components to nicely nest them when needed. However, Popover won't open when doing it (and vice versa). Here is my demo: https://codesandbox.io/p/sandbox/tooltip-popover-forked-dh3zq3?file=%2Fsrc%2FApp.tsx%3A58%2C40

While tooltip works fine, popover won't open. If I nest popover inside tooltip, then popover works but tooltip doesnt. What should I change? I would really like to have separate components for Tooltip and Popover instead of having to nest them in single component like describe here: https://www.radix-ui.com/primitives/docs/guides/composition#composing-multiple-primitives

Activity

  1. Caio-Tera commented on Jan 24, 2024

    @Caio-Tera

    I could be wrong but shouldn't your code look like this?

    import { Button } from '@/components/ui/button';
    import { Input } from '@/components/ui/input';
    import { Label } from '@/components/ui/label';
    import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
    import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
    
    export function PopoverDemo() {
      return (
        <TooltipProvider>
          <Popover>
            <PopoverTrigger asChild>
              <Tooltip>
                <TooltipTrigger asChild>
                  <Button variant="outline">Open popover</Button>
                </TooltipTrigger>
                <TooltipContent>
                  <p>Add to library</p>
                </TooltipContent>
              </Tooltip>
            </PopoverTrigger>
            <PopoverContent className="w-80">
              <div className="grid gap-4">
                <div className="space-y-2">
                  <h4 className="font-medium leading-none">Dimensions</h4>
                  <p className="text-sm text-muted-foreground">
                    Set the dimensions for the layer.
                  </p>
                </div>
                <div className="grid gap-2">
                  <div className="grid grid-cols-3 items-center gap-4">
                    <Label htmlFor="width">Width</Label>
                    <Input
                      id="width"
                      defaultValue="100%"
                      className="col-span-2 h-8"
                    />
                  </div>
                  <div className="grid grid-cols-3 items-center gap-4">
                    <Label htmlFor="maxWidth">Max. width</Label>
                    <Input
                      id="maxWidth"
                      defaultValue="300px"
                      className="col-span-2 h-8"
                    />
                  </div>
                  <div className="grid grid-cols-3 items-center gap-4">
                    <Label htmlFor="height">Height</Label>
                    <Input
                      id="height"
                      defaultValue="25px"
                      className="col-span-2 h-8"
                    />
                  </div>
                  <div className="grid grid-cols-3 items-center gap-4">
                    <Label htmlFor="maxHeight">Max. height</Label>
                    <Input
                      id="maxHeight"
                      defaultValue="none"
                      className="col-span-2 h-8"
                    />
                  </div>
                </div>
              </div>
            </PopoverContent>
          </Popover>
        </TooltipProvider>
      );
    }

    I'm sorry if I didn't understand what you meant.

  2. piszczu4 commented on Jan 24, 2024

    @piszczu4
    Author

    @Caio-Tera I'd like to create to separate components, one for TooltipButton and another one for PopoverButton and eventually use them both when needed instead of crating this long chain as you did. Why my code doesnt work, i.e. this part?:

      const tooltipButtonWithPopover = (
        <TooltipButton trigger={popoverButtonWithoutTooltip}>
          Tooltip Content
        </TooltipButton>
      );
    
  3. Caio-Tera commented on Jan 26, 2024

    @Caio-Tera

    Does this make any sense to you?

    I couldn't test whether it renders.

    But I imagine this is the beginning of a solution?

    import React from 'react';
    
    import { Button } from '@/components/ui/button';
    import { Input } from '@/components/ui/input';
    import { Label } from '@/components/ui/label';
    import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
    import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
    
    export function PopoverComponent({
      trigger,
      body,
    }: {
      trigger: React.ReactNode;
      body: React.ReactNode;
    }) {
      return (
        <Popover>
          <PopoverTrigger asChild>{trigger}</PopoverTrigger>
          <PopoverContent className="w-80">{body}</PopoverContent>
        </Popover>
      );
    }
    
    export function TooltipComponent({
      trigger,
      body,
    }: {
      trigger: React.ReactNode;
      body: React.ReactNode;
    }) {
      return (
        <Tooltip>
          <TooltipTrigger asChild>{trigger}</TooltipTrigger>
          <TooltipContent>{body}</TooltipContent>
        </Tooltip>
      );
    }
    
    export function PopoverBody() {
      return (
        <div className="grid gap-4">
          <div className="space-y-2">
            <h4 className="font-medium leading-none">Dimensions</h4>
            <p className="text-sm text-muted-foreground">
              Set the dimensions for the layer.
            </p>
          </div>
          <div className="grid gap-2">
            <div className="grid grid-cols-3 items-center gap-4">
              <Label htmlFor="width">Width</Label>
              <Input id="width" defaultValue="100%" className="col-span-2 h-8" />
            </div>
            <div className="grid grid-cols-3 items-center gap-4">
              <Label htmlFor="maxWidth">Max. width</Label>
              <Input
                id="maxWidth"
                defaultValue="300px"
                className="col-span-2 h-8"
              />
            </div>
            <div className="grid grid-cols-3 items-center gap-4">
              <Label htmlFor="height">Height</Label>
              <Input id="height" defaultValue="25px" className="col-span-2 h-8" />
            </div>
            <div className="grid grid-cols-3 items-center gap-4">
              <Label htmlFor="maxHeight">Max. height</Label>
              <Input
                id="maxHeight"
                defaultValue="none"
                className="col-span-2 h-8"
              />
            </div>
          </div>
        </div>
      );
    }
    
    export function Rootlayout() {
      return (
        <TooltipProvider>
          <PopoverComponent
            trigger={
              <TooltipComponent
                trigger={<Button variant="outline">Open popover</Button>}
                body={<p>Add to library</p>}
              />
            }
            body={<PopoverBody />}
          />
        </TooltipProvider>
      );
    }
  4. piszczu4 commented on Jan 26, 2024

    @piszczu4
    Author

    @Caio-Tera how does it differ from my current implementaion? I also splitted it into to separate components which does not work. I would appreciate if someone could actually fix my code and indicate what was wrong with him.

  5. jrandeniya commented on Feb 5, 2024

    @jrandeniya

    This worked for me - Tooltip and Popover working together:

    import {
      Tooltip,
      TooltipContent,
      TooltipTrigger,
    } from "@/components/ui/tooltip";
    
    type ToolbarTooltipProps = {
      content: string; // Or could be another React.ReactNode.
      children: React.ReactNode;
    };
    
    export const ToolbarTooltip = ({ content, children }: ToolbarTooltipProps) => {
      return (
        <Tooltip>
          <TooltipTrigger asChild>{children}</TooltipTrigger>
          <TooltipContent side="bottom" sideOffset={20}>
            {content}
          </TooltipContent>
        </Tooltip>
      );
    };
    
    "use client";
    
    import { Button } from "@/components/ui/button";
    import {
      Popover,
      PopoverContent,
      PopoverTrigger,
    } from "@/components/ui/popover";
    import { ToolbarTooltip } from "./ToolbarTooltip";
    
    export const SomeComponent = () => {
      return (
        <Popover>
          <ToolbarTooltip content="Background">
            <PopoverTrigger asChild>
              <Button variant={"ghost"} className="h-full">
                Trigger
              </Button>
            </PopoverTrigger>
          </ToolbarTooltip>
          <PopoverContent className="z-50">....</PopoverContent>
        </Popover>
      );
    };
    
  6. shadcn commented on Feb 28, 2024

    @shadcn
    Collaborator

    This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

  7. Sisableng commented on Jun 3, 2024

    @Sisableng

    in my case this is worked for me

    <TooltipProvider>
            <Popover>
              <Tooltip delayDuration={100}>
                <PopoverTrigger asChild>
                  <TooltipTrigger asChild>
                    <Button size={"icon"} variant={"ghost"}>
                      {icon}
                    </Button>
                  </TooltipTrigger>
                </PopoverTrigger>
                <TooltipContent
                  side="right"
                  className="bg-secondary font-semibold text-foreground"
                >
                  <p>{title}</p>
                  <TooltipArrow className="fill-secondary" />
                </TooltipContent>
              </Tooltip>
              <PopoverContent side="right">
                <div>{children}</div>
                <PopoverArrow className="fill-popover" />
              </PopoverContent>
            </Popover>
          </TooltipProvider>
  8. HungerDeng commented on Feb 18, 2025

    @HungerDeng

    in my case this is worked for me

    {icon}

    {title}

    {children}

    It worked for me too. Thank you very much. Though the nested order is too damn counterintuitive: TooltipProvider -> Popover -> Tooltip -> PopoverTrigger -> TooltipTrigger -> ......

  9. mttetc commented on Sep 10, 2025

    @mttetc

    in my case this is worked for me

    {icon}

    {title}

    {children}

    React.Children.only expected to receive a single React element child. :(

  10. andreastande commented on Sep 22, 2025

    @andreastande

    If you don't want the tooltip to appear when the popover is open, this worked for me:

    // A popover with tooltip.
    // Tooltip is disabled when the popover is open.
    const PopoverWithTooltip = () => {
      const [open, setOpen] = useState(false)
    
      return (
        <Popover open={open} onOpenChange={setOpen}>
          <Tooltip open={open ? false : undefined}>
            <TooltipTrigger asChild>
              <PopoverTrigger asChild>
                <Button>
                  Open popover
                </Button>
              </PopoverTrigger>
            </TooltipTrigger>
            <TooltipContent>
              Tooltip content
            </TooltipContent>
          </Tooltip>
          <PopoverContent>
            Popover content
          </PopoverContent>
        </Popover>
      )
    }

    If anyone has a solution that does not involve useState, I'd be happy if you shared it. Might be possible to use the data-attributes, but haven't tried.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions