shadcnui

How do I trigger a Shadcn Tooltip on mobile?


While testing my application on mobile screens, I noticed that the <Tooltip> component from Shadcn UI doesn't activate or display as expected on smaller devices. Tapping or long-pressing the tooltip trigger will not show the tooltip, which is what I would like the behaviour to be.

Is there a way to ensure the <Tooltip> component activates on tap on mobile screens?


Solution

  • Essentially, the <Tooltip> component won't show on mobile screens, but a <Popover> component will! You can make a <HybridTooltip> component that will show a Tooltip on desktop, and a Popover on mobile.

    First, add hybrid-tooltip.tsx to your ui folder, with the following code (adapted from Github):

    'use client';
    
    import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react';
    import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './tooltip';
    import { Popover, PopoverTrigger, PopoverContent } from './popover';
    import { TooltipContentProps, TooltipProps, TooltipProviderProps, TooltipTriggerProps } from '@radix-ui/react-tooltip';
    import { PopoverContentProps, PopoverProps, PopoverTriggerProps } from '@radix-ui/react-popover';
    
    const TouchContext = createContext<boolean | undefined>(undefined);
    const useTouch = () => useContext(TouchContext);
    
    const TouchProvider = (props: PropsWithChildren) => {
      const [isTouch, setTouch] = useState<boolean>();
    
      useEffect(() => {
        setTouch(window.matchMedia('(pointer: coarse)').matches);
      }, []);
    
      return <TouchContext.Provider value={isTouch} {...props} />;
    };
    
    const HybridTooltipProvider = (props: TooltipProviderProps) => {
      return <TooltipProvider delayDuration={0} {...props} />
    }
    
    const HybridTooltip = (props: TooltipProps & PopoverProps) => {
      const isTouch = useTouch();
    
      return isTouch ? <Popover {...props} /> : <Tooltip {...props} />;
    };
    
    const HybridTooltipTrigger = (props: TooltipTriggerProps & PopoverTriggerProps) => {
      const isTouch = useTouch();
    
      return isTouch ? <PopoverTrigger {...props} /> : <TooltipTrigger {...props} />;
    };
    
    const HybridTooltipContent = (props: TooltipContentProps & PopoverContentProps) => {
      const isTouch = useTouch();
    
      return isTouch ? <PopoverContent {...props} /> : <TooltipContent {...props} />;
    };
    
    export { TouchProvider, HybridTooltipProvider, HybridTooltip, HybridTooltipTrigger, HybridTooltipContent }
    

    Then, wrap your app with <TouchProvider> in your RootLayout:

    import { TouchProvider } from '@/components/ui/hybrid-tooltip';
    import './globals.css';
    
    export default function RootLayout({ children }: React.PropsWithChildren) {
      return (
        <html lang='en'>
          <body>
            <TouchProvider>
              <main>{children}</main>
            </TouchProvider>
          </body>
        </html>
      );
    }
    

    Finally, you can use the <HybridTooltip> component, in place of a regular Tooltip:

    import { HybridTooltipProvider, HybridTooltip, HybridTooltipTrigger, HybridTooltipContent } from "./ui/hybrid-tooltip";
    
    // ...
    
    <HybridTooltipProvider>
      <HybridTooltip>
        <HybridTooltipTrigger>
          Trigger text
        </HybridTooltipTrigger>
        <HybridTooltipContent>
          <p>Tooltip Contents</p>
        </HybridTooltipContent>
      </HybridTooltip>
    </HybridTooltipProvider>