tooltip

Tooltip

Short helper text on hover/focus.

Tooltip Examples

Copy-ready examples for controlled tooltips (plain and rich).

Controlled (click toggle)

Tooltip does not manage state. You control `isOpen` from the parent.

Live preview

Save to favorites
Tip: Tooltip is controlled. You decide when to open/close (click, hover, focus, etc.).

Basic controlled tooltip

"use client";

import { useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [open, setOpen] = useState(false);

  return (
    <Tooltip isOpen={open} position="top" contentBody="Save to favorites">
      <Button ripple outline onClick={() => setOpen((v) => !v)}>
        Toggle
      </Button>
    </Tooltip>
  );
}

Hover + focus pattern

Open on hover/focus and close on mouse leave/blur (recommended for desktop).

Live preview

This pattern opens on hover and focus. It also closes on mouse leave and blur.
Hover me

Hover + focus

"use client";

import { useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [open, setOpen] = useState(false);

  return (
    <span
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)}
      onBlur={() => setOpen(false)}
      style={{ display: "inline-flex" }}
    >
      <Tooltip isOpen={open} position="top" contentBody="Hover me">
        <Button ripple outline>Hover / Focus</Button>
      </Tooltip>
    </span>
  );
}

Rich tooltip (title + footer)

Use `type='rich'` to enable the header and footer layout.

Live preview

Keyboard shortcutPress ⌘K to open search.
Works everywhere in the app.

Rich tooltip

"use client";

import { useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [open, setOpen] = useState(true);

  return (
    <Tooltip
      isOpen={open}
      type="rich"
      style="filled"
      variants="neutral"
      position="right"
      title="Keyboard shortcut"
      contentBody="Press ⌘K to open search."
      contentFooter={
        <div style={{ display: "flex", gap: 8 }}>
          <Button ripple outline size="small" onClick={() => setOpen(false)}>Close</Button>
          <Button ripple outline size="small" variant="primary" onClick={() => setOpen(false)}>Got it</Button>
        </div>
      }
    >
      <Button ripple outline onClick={() => setOpen((v) => !v)}>Toggle rich</Button>
    </Tooltip>
  );
}

Position (top/right/bottom/left)

Position maps to `data-side` for CSS. Useful for layout testing.

Live preview

Anchor
Position: top

All positions

"use client";

import { useMemo, useState } from "react";
import Tooltip from "@/_components/tooltip/Tooltip";

export default function Example() {
  const [idx, setIdx] = useState(0);
  const positions = useMemo(() => ["top", "right", "bottom", "left"] as const, []);

  return (
    <>
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
        {positions.map((p, i) => (
          <button key={p} type="button" onClick={() => setIdx(i)}>
            {p}
          </button>
        ))}
      </div>

      <div style={{ display: "grid", placeItems: "center", minHeight: 120 }}>
        <Tooltip isOpen position={positions[idx]} contentBody={"Position: " + positions[idx]}>
          <span style={{ padding: 12, border: "1px solid rgba(0,0,0,0.12)", borderRadius: 12 }}>Anchor</span>
        </Tooltip>
      </div>
    </>
  );
}

Variants + styles

Combine `variants` and `style` to match semantic tones and container rules.

Live preview

Primary filledPrimary outlinedNeutral inverse
secondary outlinedtertiary outlinedsuccess outlinedwarning outlineddanger outlinedinfo outlined

Filled / outlined / inverse

import Tooltip from "@/_components/tooltip/Tooltip";

export default function Example() {
  return (
    <>
      <Tooltip isOpen type="plain" style="filled" variants="primary" position="top" contentBody="Primary filled">
        <span>Anchor</span>
      </Tooltip>

      <Tooltip isOpen type="plain" style="outlined" variants="primary" position="top" contentBody="Primary outlined">
        <span>Anchor</span>
      </Tooltip>

      <Tooltip isOpen type="plain" style="filled" variants="neutral-inverse" position="top" contentBody="Neutral inverse">
        <span>Anchor</span>
      </Tooltip>
    </>
  );
}

Long content (wrap test)

Use longer body text to validate wrapping and max width.

Live preview

InfoThis is a longer tooltip body used to validate wrapping, max-inline-size, and layout stability on smaller viewports.

Long body

"use client";

import { useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";

export default function Example() {
  const [open, setOpen] = useState(true);

  return (
    <Tooltip
      isOpen={open}
      type="rich"
      style="outlined"
      variants="info"
      position="bottom"
      title="Info"
      contentBody="Long tooltip body used to validate wrapping and max-inline-size."
      contentFooter={<Button ripple outline size="small" onClick={() => setOpen(false)}>Close</Button>}
    >
      <Button ripple outline variant="info" onClick={() => setOpen((v) => !v)}>Toggle</Button>
    </Tooltip>
  );
}

Icon-only anchor

Common UI pattern: tooltip on icon buttons (hover/focus controlled externally).

Live preview

Settings

Icon-only tooltip

"use client";

import { useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";

export default function Example() {
  const [open, setOpen] = useState(false);

  return (
    <span
      onMouseEnter={() => setOpen(true)}
      onMouseLeave={() => setOpen(false)}
      onFocus={() => setOpen(true)}
      onBlur={() => setOpen(false)}
      style={{ display: "inline-flex" }}
    >
      <Tooltip isOpen={open} position="top" contentBody="Settings">
        <Button ripple variant="transparent" forceHeight="40px" forceWidth="40px" iconContent={<Icon name="settings" />} />
      </Tooltip>
    </span>
  );
}

Many tooltips (single open state)

Useful when you have a toolbar/action list and only one tooltip should be open at a time.

Live preview

Pattern: only one tooltip open at a time (controlled by a single state).
CopyEditDangerThis action is irreversible.Share

One-open-at-a-time

"use client";

import { useMemo, useState } from "react";
import Tooltip from "@/packages/ui/src/_components/tooltip/Tooltip";
import { Button } from "@/packages/ui/src/_components/buttons_variants/buttons/Button";
import Icon from "@/packages/ui/src/_components/icon/Icon";

export default function Example() {
  const [openId, setOpenId] = useState<string | null>("t-2");

  const items = useMemo(
    () => [
      { id: "t-1", label: "Copy", icon: "content_copy" },
      { id: "t-2", label: "Edit", icon: "edit" },
      { id: "t-3", label: "Delete", icon: "delete" },
      { id: "t-4", label: "Share", icon: "share" },
    ],
    []
  );

  return (
    <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
      {items.map((it) => (
        <Tooltip
          key={it.id}
          isOpen={openId === it.id}
          variants={it.id === "t-3" ? "danger" : "neutral"}
          type={it.id === "t-3" ? "rich" : "plain"}
          title={it.id === "t-3" ? "Danger" : undefined}
          contentBody={it.id === "t-3" ? "This action is irreversible." : it.label}
          position="top"
        >
          <Button
            ripple
            outline
            variant={it.id === "t-3" ? "danger" : "transparent"}
            forceHeight="40px"
            forceWidth="40px"
            iconContent={<Icon name={it.icon} />}
            onClick={() => setOpenId((v) => (v === it.id ? null : it.id))}
          />
        </Tooltip>
      ))}
    </div>
  );
}

On this page

Tooltip Props

Tooltip — Props
PropTypeRequiredDefaultDescription
isOpenbooleanYesControls tooltip visibility. Sets `data-state='open|closed'` on the tooltip panel.
childrenReact.ReactNodeYesAnchor content. Tooltip is positioned relative to this wrapper.
position"top" | "bottom" | "left" | "right"No"top"Sets `data-side` (`top/bottom/left/right`) for CSS positioning.
type"plain" | "rich"No"plain"When `rich`, enables optional header/footer layout.
style"filled" | "outlined"No"filled"Visual style. When `outlined`, adds `cssnt-tooltip--outline`.
variants"neutral" | "neutral-inverse" | "primary" | "secondary" | "tertiary" | "info" | "success" | "warning" | "danger"No"neutral"Semantic tone class (`cssnt-tooltip--*`).
titlestringNoundefinedHeader title. Rendered only when `type !== 'plain'` and `title` is provided.
contentBodyReact.ReactNode | stringNoundefinedMain tooltip content (rendered inside `.cssnt-tooltip__body`).
contentFooterReact.ReactNode | stringNoundefinedFooter content. Rendered only when `type !== 'plain'` and provided.
onChange(nextOpen: boolean) => voidNoundefinedDeclared in types, but not used by the current implementation. Prefer controlling state externally.