Tooltip
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
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
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
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
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
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
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
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
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
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| isOpen | boolean | Yes | — | Controls tooltip visibility. Sets `data-state='open|closed'` on the tooltip panel. |
| children | React.ReactNode | Yes | — | Anchor 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--*`). |
| title | string | No | undefined | Header title. Rendered only when `type !== 'plain'` and `title` is provided. |
| contentBody | React.ReactNode | string | No | undefined | Main tooltip content (rendered inside `.cssnt-tooltip__body`). |
| contentFooter | React.ReactNode | string | No | undefined | Footer content. Rendered only when `type !== 'plain'` and provided. |
| onChange | (nextOpen: boolean) => void | No | undefined | Declared in types, but not used by the current implementation. Prefer controlling state externally. |
Components