# Tooltip

Import: `import { Tooltip } from '@neo4j-ndl/react'`

## Props

### Tooltip

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `autoUpdateOptions` | `AutoUpdateOptions` |  |  |  |
| `children` | `ReactNode` | ✅ |  | Content displayed inside the tooltip. Wraps `Tooltip.Trigger`, `Tooltip.Content`, and optional parts |
| `floatingStrategy` | `'absolute' \| 'fixed'` |  |  | Positioning strategy of the tooltip. Defaults to `fixed` when inside a dialog, otherwise `absolute` |
| `followCursor` | `boolean` |  |  | Whether the tooltip should follow the mouse cursor. Only applies to 'simple' tooltips with mouse hover. |
| `hoverDelay` | `{ open: number; close: number; }` |  |  | Hover open/close delays in milliseconds |
| `isDisabled` | `boolean` |  | `false` | Whether the tooltip is disabled |
| `isInitialOpen` | `boolean` |  |  | Whether the tooltip should be open on first render |
| `isOpen` | `boolean` |  |  | Controls the open state . If omitted, the tooltip manages its own state |
| `isPortaled` | `boolean` |  |  | Whether the content is rendered in a portal. |
| `onOpenChange` | `((open: boolean, event?: Event, reason?: OpenChangeReason) => void) \| undefined` |  |  | Callback fired when the open state changes, if omitted, the tooltip manages its own state using internal hooks |
| `placement` | `'bottom-end' \| 'bottom-start' \| 'bottom' \| 'left-end' \| 'left-start' \| 'left' \| 'right-end' \| 'right-start' \| 'right' \| 'top-end' \| 'top-start' \| 'top'` |  |  | Placement of the tooltip content relative to the trigger |
| `shouldCloseOnReferenceClick` | `boolean` |  |  | Whether the tooltip should close when the reference element is clicked. Useful when the trigger moves after click. |
| `type` | `'rich' \| 'simple'` | ✅ |  | Visual style of the tooltip content. `simple` renders plain text, `rich` renders a composable panel @deprecated rich type will be removed in v5, use simple type instead. |

### Tooltip.Actions

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | Content displayed inside the actions container |
| `ref` | `Ref<HTMLDivElement>` |  |  | A ref to apply to the root element. |

### Tooltip.Body

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | Content displayed inside the body |
| `passThroughProps` | `Partial<Omit<TypographyProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>, "ref">>` |  |  | Additional props to pass to the Typography component |
| `ref` | `Ref<HTMLSpanElement>` |  |  | A ref to apply to the root element. |

### Tooltip.Content

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | Content displayed inside the tooltip |
| `ref` | `Ref<HTMLDivElement>` |  |  | A ref to apply to the root element. |

### Tooltip.Header

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | Content displayed inside the header |
| `passThroughProps` | `Partial<Omit<TypographyProps & { as?: ElementType<any, keyof IntrinsicElements>; } & BaseProps<ElementType<any, keyof IntrinsicElements>>, "ref">>` |  |  | @deprecated Use `typographyVariant` instead |
| `ref` | `Ref<HTMLSpanElement>` |  |  | A ref to apply to the root element. |
| `typographyVariant` | `'body-large' \| 'body-medium' \| 'body-small' \| 'code' \| 'display' \| 'label' \| 'subheading-large' \| 'subheading-medium' \| 'subheading-small' \| 'title-1' \| 'title-2' \| 'title-3' \| 'title-4'` |  | `subheading-medium` |  |

### Tooltip.Trigger

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | Content displayed inside the trigger element |
| `hasButtonWrapper` | `boolean` |  | `false` | Whether the trigger element can be any element without an extra button wrapper. The child element will be cloned with the necessary trigger props and merged refs |
| `ref` | `Ref<HTMLButtonElement>` |  |  | A ref to apply to the root element. |

## Accessibility

## Keyboard interactions

Behavior depends on the `type` prop. Simple and rich tooltips follow different interaction models.

### Simple tooltips (`type="simple"`)

Implements the keyboard interactions defined in the [WAI-ARIA tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).

| Key | Description |
|-----|-------------|
| `Tab` | Moves focus to the trigger; the tooltip opens when the trigger receives focus |
| `Shift + Tab` | Moves focus away from the trigger; the tooltip closes when focus leaves |
| `Escape` | Closes the tooltip while focus remains on the trigger |

Simple tooltips also open on mouse hover and close when the pointer leaves the trigger and tooltip area.

### Rich tooltips (`type="rich"`)

Rich tooltips use a light-dismiss dialog interaction model for panels with actions or structured content. It doesn't trap focus, and clicking outside it will immediately close the component.

| Key | Description |
|-----|-------------|
| `Tab` | When closed, moves focus to the trigger. When open, moves focus between focusable elements inside the tooltip |
| `Shift + Tab` | When open, moves focus between focusable elements. Tabbing past the last element closes the tooltip and returns focus to the trigger |
| `Space` | On the trigger, toggles the tooltip open and closed. When open, activates the focused button |
| `Enter` | On the trigger, toggles the tooltip open (same as click) |
| `Escape` | Closes the tooltip and returns focus to the trigger |

## WAI-ARIA roles and attributes

### Simple tooltips

Implements the [WAI-ARIA tooltip pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/).

- The popup (`Tooltip.Content`) has `role="tooltip"`
- When open, the trigger is described via `aria-describedby`, wired by Floating UI to the portaled tooltip element

### Rich tooltips

- The popup (`Tooltip.Content`) has `role="dialog"`
- The popup is labeled via `aria-labelledby` (auto-wired from `Tooltip.Header`) or `aria-label`

## Implementation guidelines

- Rich tooltips must have an accessible name — provide `Tooltip.Header` or set `aria-label` on `Tooltip.Content` via `htmlAttributes`. A console warning is emitted if neither is provided
- Use `hasButtonWrapper` on `Tooltip.Trigger` with Needle trigger buttons (`IconButton`, `FilledButton`, etc.) — accessible names are already provided via `description` or visible label text
- Do not rely on a simple tooltip as the only source of essential information — provide a persistent accessible name or description on the trigger when the content is required to operate the control
- Rich tooltips with `Tooltip.Actions` should use Needle `Button` variants (e.g. `OutlinedButton`, `TextButton`) or `TextLink` for actions so they are keyboard focusable, styled consistently, and operable
- Set `isDisabled` when the tooltip would open alongside another overlay (e.g. an open menu), which can create confusing or conflicting information for assistive technology users

### Related WCAG criteria

- [1.4.13 Content on Hover or Focus](https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html) (AA): Simple and rich tooltips can be dismissed with `Escape`; rich tooltips can also be dismissed by activating the trigger again
- [2.1.1 Keyboard](https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html) (A): Simple tooltips are available to keyboard users via focus; rich tooltips and their actions are fully operable via keyboard
- [4.1.2 Name, Role, Value](https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html) (A): `role="tooltip"` / `role="dialog"`, accessible name (`aria-labelledby` / `aria-label` for rich tooltips), and accessible description (`aria-describedby` for simple tooltips when open) are set automatically by the component

## Examples

### Default

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple">
      <Tooltip.Trigger>Hover me!</Tooltip.Trigger>
      <Tooltip.Content>This is a tooltip!</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Controlled

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Tooltip } from '@neo4j-ndl/react';
import { useState } from 'react';

const Component = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <Tooltip type="simple" isOpen={isOpen}>
      <Tooltip.Trigger hasButtonWrapper>
        <FilledButton size="medium" onClick={() => setIsOpen((prev) => !prev)}>
          Click me
        </FilledButton>
      </Tooltip.Trigger>
      <Tooltip.Content>This is a controlled tooltip</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Disabled

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple" isDisabled={true}>
      <Tooltip.Trigger>Hover me!</Tooltip.Trigger>
      <Tooltip.Content>This is a tooltip!</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Follow Cursor

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Tooltip } from '@neo4j-ndl/react';

const triggerStyle: React.CSSProperties = {
  alignItems: 'center',
  backgroundColor: 'var(--theme-color-primary-bg-weak)',
  borderRadius: '4px',
  color: 'var(--theme-color-primary-text-inverse)',
  display: 'flex',
  height: '300px',
  justifyContent: 'center',
  padding: '8px',
  width: '300px',
};

const Component = () => {
  return (
    <div style={{ display: 'flex', gap: '24px' }}>
      <Tooltip type="simple" followCursor placement="bottom">
        <Tooltip.Trigger>
          <div style={triggerStyle}>Hover me (x-axis)</div>
        </Tooltip.Trigger>
        <Tooltip.Content>Follows cursor along x-axis</Tooltip.Content>
      </Tooltip>

      <Tooltip type="simple" followCursor placement="right">
        <Tooltip.Trigger>
          <div style={triggerStyle}>Hover me (y-axis)</div>
        </Tooltip.Trigger>
        <Tooltip.Content>Follows cursor along y-axis</Tooltip.Content>
      </Tooltip>
    </div>
  );
};

export default Component;
```

### Hover Delay

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple" hoverDelay={{ close: 200, open: 200 }}>
      <Tooltip.Trigger>Hover me!</Tooltip.Trigger>
      <Tooltip.Content>This is a tooltip!</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### In Dialog

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Dialog, FilledButton, Tooltip, Typography } from '@neo4j-ndl/react';
import { useState } from 'react';

const Component = () => {
  const [isDialogOpen, setDialogOpen] = useState(false);
  const closeDialog = () => setDialogOpen(false);
  return (
    <div className="n-flex n-justify-center">
      <FilledButton onClick={() => setDialogOpen(true)}>
        Open Dialog
      </FilledButton>
      <Dialog isOpen={isDialogOpen} onClose={closeDialog}>
        <Dialog.Header>Header</Dialog.Header>
        <Dialog.Content>
          <Tooltip type="simple">
            <Tooltip.Trigger>
              <Typography variant="body-medium">Hover me!</Typography>
            </Tooltip.Trigger>
            <Tooltip.Content>This is a long tooltip!</Tooltip.Content>
          </Tooltip>
        </Dialog.Content>
      </Dialog>
    </div>
  );
};

export default Component;
```

### Initial Open

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple" isInitialOpen={true}>
      <Tooltip.Trigger>Hover me!</Tooltip.Trigger>
      <Tooltip.Content>This is a tooltip!</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Placements

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  const placements = [
    { label: 'Top', placement: 'top' as const },
    { label: 'Bottom', placement: 'bottom' as const },
    { label: 'Left', placement: 'left' as const },
    { label: 'Right', placement: 'right' as const },
    { label: 'Top Start', placement: 'top-start' as const },
    { label: 'Top End', placement: 'top-end' as const },
    { label: 'Bottom Start', placement: 'bottom-start' as const },
    { label: 'Bottom End', placement: 'bottom-end' as const },
  ];

  return (
    <div className="n-grid n-grid-cols-4 n-gap-token-16 n-p-token-16 n-place-items-center">
      {placements.map(({ placement, label }) => (
        <Tooltip key={placement} type="simple" placement={placement}>
          <Tooltip.Trigger hasButtonWrapper>
            <FilledButton size="small">{label}</FilledButton>
          </Tooltip.Trigger>
          <Tooltip.Content>Tooltip on {label.toLowerCase()}</Tooltip.Content>
        </Tooltip>
      ))}
    </div>
  );
};

export default Component;
```

### Rich

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="rich">
      <Tooltip.Trigger hasButtonWrapper>
        <FilledButton size="medium">Click me!</FilledButton>
      </Tooltip.Trigger>
      <Tooltip.Content>
        <Tooltip.Header>Header</Tooltip.Header>
        <Tooltip.Body>
          This rich tooltip demonstrates all the available subcomponents: Header
          and Body, without Actions.
        </Tooltip.Body>
      </Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Rich Actions

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import {
  FilledButton,
  OutlinedButton,
  TextButton,
  Tooltip,
} from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="rich">
      <Tooltip.Trigger hasButtonWrapper>
        <FilledButton size="medium">Click me!</FilledButton>
      </Tooltip.Trigger>
      <Tooltip.Content>
        <Tooltip.Header>Header</Tooltip.Header>
        <Tooltip.Body>
          This rich tooltip demonstrates all the available subcomponents:
          Header, Body, and Actions.
        </Tooltip.Body>
        <Tooltip.Actions>
          <OutlinedButton onClick={() => alert('Primary action')}>
            Action
          </OutlinedButton>
          <TextButton onClick={() => alert('Secondary action')}>
            Action
          </TextButton>
        </Tooltip.Actions>
      </Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Rich Link

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, TextLink, Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="rich">
      <Tooltip.Trigger hasButtonWrapper>
        <FilledButton size="medium">Click me!</FilledButton>
      </Tooltip.Trigger>
      <Tooltip.Content>
        <Tooltip.Header>Header</Tooltip.Header>
        <Tooltip.Body>
          This rich tooltip demonstrates all the available subcomponents:
          Header, Body, and Actions.
        </Tooltip.Body>
        <Tooltip.Actions>
          <TextLink type="external" href="#">
            External link
          </TextLink>
        </Tooltip.Actions>
      </Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### Trigger Button Wrapper

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple">
      <Tooltip.Trigger hasButtonWrapper>
        <FilledButton size="medium">Click me!</FilledButton>
      </Tooltip.Trigger>
      <Tooltip.Content>This is a tooltip!</Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```

### With Keyboard Display

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Kbd, Tooltip } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Tooltip type="simple">
      <Tooltip.Trigger>Hover me!</Tooltip.Trigger>
      <Tooltip.Content>
        This is a keyboard shortcut:{' '}
        <Kbd modifierKeys={['meta']} keys={['B']} />
      </Tooltip.Content>
    </Tooltip>
  );
};

export default Component;
```
