Tooltip

A popup component that displays contextual information when hovering or focusing on a trigger element.

Docs
import { Tooltip, TooltipTrigger, TooltipContent } from "~/components/tooltip";

export default function TooltipBasicDemo() {
  return (
    <Tooltip>
      <TooltipTrigger>Hover me</TooltipTrigger>
      <TooltipContent>Tooltip content</TooltipContent>
    </Tooltip>
  );
}

Installation

CLI

Run the following command to add the component to your project:

npx @ark-preset/cli@latest add tooltip

Manual

Create the recipe file at src/components/recipes/tooltip.ts:

import { tv, type VariantProps } from "tailwind-variants";

export const tooltipVariants = tv({
  slots: {
    root: "inline-flex",
    trigger:
      "inline-flex items-center justify-center gap-2 rounded-md border border-input bg-transparent px-4 py-2 text-sm font-medium text-foreground ring-offset-background transition-colors hover:bg-accent max-sm:select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
    positioner: "z-50",
    content:
      "z-50 overflow-hidden rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
    arrow: "",
    arrowTip: "h-2 w-2 border-t border-l border-border bg-popover",
  },
});

export type TooltipVariants = VariantProps<typeof tooltipVariants>;

Create the component directory and files.

src/components/tooltip/tooltip.base.tsx:

import { Tooltip as ArkTooltip } from "@ark-ui/solid/tooltip";
import { splitProps, type Component } from "solid-js";
import { buttonVariants, tooltipVariants, type ButtonVariants } from "../recipes/tooltip";

const styles = tooltipVariants();

const Root = ArkTooltip.Root;
const RootProvider = ArkTooltip.RootProvider;

const Trigger: Component<ArkTooltip.TriggerProps & ButtonVariants> = (props) => {
  const [local, others] = splitProps(props, ["class", "variant", "size"]);
  return (
    <ArkTooltip.Trigger
      class={buttonVariants({ variant: local.variant, size: local.size, class: local.class })}
      {...others}
    />
  );
};

const Positioner: Component<ArkTooltip.PositionerProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkTooltip.Positioner class={styles.positioner({ class: local.class })} {...others} />;
};

const Content: Component<ArkTooltip.ContentProps> = (props) => {
  const [local, others] = splitProps(props, ["class", "style"]);
  return (
    <ArkTooltip.Content
      class={styles.content({ class: local.class })}
      {...others}
      style={`--arrow-size: 10px; ${local.style}`}
    />
  );
};

const Arrow: Component<ArkTooltip.ArrowProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkTooltip.Arrow class={styles.arrow({ class: local.class })} {...others} />;
};

const ArrowTip: Component<ArkTooltip.ArrowTipProps> = (props) => {
  const [local, others] = splitProps(props, ["class"]);
  return <ArkTooltip.ArrowTip class={styles.arrowTip({ class: local.class })} {...others} />;
};

export const Tooltip = {
  Root,
  RootProvider,
  Trigger,
  Positioner,
  Content,
  Arrow,
  ArrowTip,
};

src/components/tooltip/index.tsx:

import { splitProps, type Component } from "solid-js";
import { Tooltip as TooltipBase } from "./tooltip.base";
import { Tooltip as ArkTooltip } from "@ark-ui/solid/tooltip";

const Tooltip = TooltipBase.Root;

const TooltipTrigger = TooltipBase.Trigger;

type TooltipContentProps = ArkTooltip.ContentProps & {
  /** When true, renders an arrow pointing to the trigger element */
  useArrow?: boolean;
};

const TooltipContent: Component<TooltipContentProps> = (props) => {
  const [local, others] = splitProps(props, ["useArrow", "children"]);
  return (
    <TooltipBase.Positioner>
      <TooltipBase.Content {...others}>
        {local.useArrow && (
          <TooltipBase.Arrow>
            <TooltipBase.ArrowTip />
          </TooltipBase.Arrow>
        )}
        {local.children}
      </TooltipBase.Content>
    </TooltipBase.Positioner>
  );
};

export { Tooltip, TooltipTrigger, TooltipContent, TooltipBase };

export { tooltipVariants, type TooltipVariants } from "../recipes/tooltip";

Note: Make sure your project has the Tailwind CSS theme variables set up (--background, --foreground, --ring, --border, etc.) or override the utility classes to match your design system.

Dependencies: This component imports shared recipes or sub-components from other packages. Make sure the following are also installed: button.

Usage

Basic Usage

import { Tooltip, TooltipTrigger, TooltipContent } from "~/components/tooltip";

export default function TooltipBasicDemo() {
  return (
    <Tooltip>
      <TooltipTrigger>Hover me</TooltipTrigger>
      <TooltipContent>Tooltip content</TooltipContent>
    </Tooltip>
  );
}

With Arrow

Add an arrow pointing to the trigger element.

import { Tooltip, TooltipTrigger, TooltipContent } from "~/components/tooltip";

export default function TooltipArrowDemo() {
  return (
    <div class="flex justify-center">
      <Tooltip>
        <TooltipTrigger>With Arrow</TooltipTrigger>
        <TooltipContent useArrow>Tooltip with arrow</TooltipContent>
      </Tooltip>
    </div>
  );
}

Controlled Open State

Use open and onOpenChange to control the tooltip visibility externally:

import { createSignal } from "solid-js";
import { Tooltip, TooltipTrigger, TooltipContent } from "~/components/tooltip";

export default function TooltipControlledDemo() {
  const [open, setOpen] = createSignal(false);
  return (
    <div class="flex flex-col items-center gap-4">
      <button
        class="inline-flex items-center justify-center gap-2 rounded-md border border-input bg-transparent px-4 py-2 text-sm font-medium text-foreground ring-offset-background transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
        onClick={() => setOpen((v) => !v)}
      >
        {open() ? "Close" : "Open"} tooltip
      </button>
      <Tooltip open={open()} onOpenChange={(e: { open: boolean }) => setOpen(e.open)}>
        <TooltipTrigger>Hover or click above</TooltipTrigger>
        <TooltipContent>Controlled tooltip</TooltipContent>
      </Tooltip>
    </div>
  );
}

Advanced Usage

When the composite Tooltip doesn’t provide enough control, import the raw primitive parts via TooltipBase:

import { TooltipBase } from "~/components/tooltip";

Or import directly from the base file:

import { Tooltip } from "~/components/tooltip/tooltip.base";

Manual Arrow Composition

For custom arrow styling or positioning, compose the parts manually using TooltipBase:

import { Tooltip, TooltipBase } from "~/components/tooltip";

export default function TooltipManualArrowDemo() {
  return (
    <div class="flex justify-center">
      <Tooltip>
        <TooltipBase.Trigger>Manual Arrow</TooltipBase.Trigger>
        <TooltipBase.Positioner>
          <TooltipBase.Content>
            <TooltipBase.Arrow class="[--arrow-size:14px]">
              <TooltipBase.ArrowTip />
            </TooltipBase.Arrow>
            Custom arrow size
          </TooltipBase.Content>
        </TooltipBase.Positioner>
      </Tooltip>
    </div>
  );
}

API Reference

See the Ark UI Tooltip documentation.