beta

Tooltip

A tooltip displays a description of an element on hover or focus.

installyarn add react-aria-components
version1.0.0-beta.2
usageimport {Tooltip} from 'react-aria-components'

Example#


import {Button, OverlayArrow, Tooltip, TooltipTrigger} from 'react-aria-components';

<TooltipTrigger>
  <Button>✏️</Button>
  <Tooltip>
    <OverlayArrow>
      <svg width={8} height={8}>
        <path d="M0 0,L4 4,L8 0" />
      </svg>
    </OverlayArrow>
    Edit
  </Tooltip>
</TooltipTrigger>
import {
  Button,
  OverlayArrow,
  Tooltip,
  TooltipTrigger
} from 'react-aria-components';

<TooltipTrigger>
  <Button>✏️</Button>
  <Tooltip>
    <OverlayArrow>
      <svg width={8} height={8}>
        <path d="M0 0,L4 4,L8 0" />
      </svg>
    </OverlayArrow>
    Edit
  </Tooltip>
</TooltipTrigger>
import {
  Button,
  OverlayArrow,
  Tooltip,
  TooltipTrigger
} from 'react-aria-components';

<TooltipTrigger>
  <Button>✏️</Button>
  <Tooltip>
    <OverlayArrow>
      <svg
        width={8}
        height={8}
      >
        <path d="M0 0,L4 4,L8 0" />
      </svg>
    </OverlayArrow>
    Edit
  </Tooltip>
</TooltipTrigger>
Show CSS
.react-aria-Tooltip {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 4px;
  background: slateblue;
  color: white;
  outline: none;
  padding: 2px 8px;
  max-width: 150px;

  &[data-placement=top] {
    margin-bottom: 8px;
    --origin: translateY(4px);
  }

  &[data-placement=bottom] {
    margin-top: 8px;
    --origin: translateY(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    margin-left: 8px;
    --origin: translateX(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    margin-right: 8px;
    --origin: translateX(4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  & .react-aria-OverlayArrow svg {
    display: block;
    fill: slateblue;
  }

  &[data-entering] {
    animation: slide 200ms;
  }

  &[data-exiting] {
    animation: slide 200ms reverse ease-in;
  }
}

@keyframes slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.react-aria-Button {
  background: var(--spectrum-global-color-gray-50);
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 4px;
  color: var(--spectrum-alias-text-color);
  appearance: none;
  vertical-align: middle;
  font-size: 1.2rem;
  text-align: center;
  margin: 0;
  outline: none;
  padding: 6px;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--spectrum-global-color-gray-500);
  }

  &[data-pressed] {
    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
    background: var(--spectrum-global-color-gray-100);
    border-color: var(--spectrum-global-color-gray-600);
  }

  &[data-focus-visible] {
    border-color: slateblue;
    box-shadow: 0 0 0 1px slateblue;
  }
}

@media (forced-colors: active) {
  .react-aria-Tooltip {
    border: 1px solid ButtonBorder;
    background: ButtonFace;
    color: ButtonText;

    & .react-aria-OverlayArrow svg {
      fill: ButtonFace;
      stroke: ButtonBorder;
    }
  }
}
.react-aria-Tooltip {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 4px;
  background: slateblue;
  color: white;
  outline: none;
  padding: 2px 8px;
  max-width: 150px;

  &[data-placement=top] {
    margin-bottom: 8px;
    --origin: translateY(4px);
  }

  &[data-placement=bottom] {
    margin-top: 8px;
    --origin: translateY(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    margin-left: 8px;
    --origin: translateX(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    margin-right: 8px;
    --origin: translateX(4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  & .react-aria-OverlayArrow svg {
    display: block;
    fill: slateblue;
  }

  &[data-entering] {
    animation: slide 200ms;
  }

  &[data-exiting] {
    animation: slide 200ms reverse ease-in;
  }
}

@keyframes slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.react-aria-Button {
  background: var(--spectrum-global-color-gray-50);
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 4px;
  color: var(--spectrum-alias-text-color);
  appearance: none;
  vertical-align: middle;
  font-size: 1.2rem;
  text-align: center;
  margin: 0;
  outline: none;
  padding: 6px;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--spectrum-global-color-gray-500);
  }

  &[data-pressed] {
    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
    background: var(--spectrum-global-color-gray-100);
    border-color: var(--spectrum-global-color-gray-600);
  }

  &[data-focus-visible] {
    border-color: slateblue;
    box-shadow: 0 0 0 1px slateblue;
  }
}

@media (forced-colors: active) {
  .react-aria-Tooltip {
    border: 1px solid ButtonBorder;
    background: ButtonFace;
    color: ButtonText;

    & .react-aria-OverlayArrow svg {
      fill: ButtonFace;
      stroke: ButtonBorder;
    }
  }
}
.react-aria-Tooltip {
  box-shadow: 0 8px 20px rgba(0 0 0 / 0.1);
  border-radius: 4px;
  background: slateblue;
  color: white;
  outline: none;
  padding: 2px 8px;
  max-width: 150px;

  &[data-placement=top] {
    margin-bottom: 8px;
    --origin: translateY(4px);
  }

  &[data-placement=bottom] {
    margin-top: 8px;
    --origin: translateY(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(180deg);
    }
  }

  &[data-placement=right] {
    margin-left: 8px;
    --origin: translateX(-4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(90deg);
    }
  }

  &[data-placement=left] {
    margin-right: 8px;
    --origin: translateX(4px);
    & .react-aria-OverlayArrow svg {
      transform: rotate(-90deg);
    }
  }

  & .react-aria-OverlayArrow svg {
    display: block;
    fill: slateblue;
  }

  &[data-entering] {
    animation: slide 200ms;
  }

  &[data-exiting] {
    animation: slide 200ms reverse ease-in;
  }
}

@keyframes slide {
  from {
    transform: var(--origin);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.react-aria-Button {
  background: var(--spectrum-global-color-gray-50);
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 4px;
  color: var(--spectrum-alias-text-color);
  appearance: none;
  vertical-align: middle;
  font-size: 1.2rem;
  text-align: center;
  margin: 0;
  outline: none;
  padding: 6px;
  transition: border-color 200ms;

  &[data-hovered] {
    border-color: var(--spectrum-global-color-gray-500);
  }

  &[data-pressed] {
    box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
    background: var(--spectrum-global-color-gray-100);
    border-color: var(--spectrum-global-color-gray-600);
  }

  &[data-focus-visible] {
    border-color: slateblue;
    box-shadow: 0 0 0 1px slateblue;
  }
}

@media (forced-colors: active) {
  .react-aria-Tooltip {
    border: 1px solid ButtonBorder;
    background: ButtonFace;
    color: ButtonText;

    & .react-aria-OverlayArrow svg {
      fill: ButtonFace;
      stroke: ButtonBorder;
    }
  }
}

Features#


The HTML title attribute can be used to create a tooltip, but it cannot be styled. Custom styled tooltips can be hard to build in an accessible way. TooltipTrigger and Tooltip help build fully accessible tooltips that can be styled as needed.

  • Styleable – States for entry and exit animations are included for easy styling, and an optional arrow element can be rendered.
  • Accessible – The trigger element is automatically associated with the tooltip via aria-describedby. Tooltips are displayed when an element receives focus.
  • Hover behavior – Tooltips display after a global delay on hover of the first tooltip, with no delay on subsequent tooltips to avoid unintended flickering. Emulated hover events on touch devices are ignored.
  • Positioning – The tooltip is positioned relative to the trigger element, and automatically flips and adjusts to avoid overlapping with the edge of the browser window.

Anatomy#


FlipTooltipTooltipTrigger

A tooltip consists of two parts: the trigger element and the tooltip itself. Users may reveal the tooltip by hovering or focusing the trigger.

import {Button, OverlayArrow, Tooltip, TooltipTrigger} from 'react-aria-components';

<TooltipTrigger>
  <Button />
  <Tooltip>
    <OverlayArrow />
  </Tooltip>
</TooltipTrigger>
import {
  Button,
  OverlayArrow,
  Tooltip,
  TooltipTrigger
} from 'react-aria-components';

<TooltipTrigger>
  <Button />
  <Tooltip>
    <OverlayArrow />
  </Tooltip>
</TooltipTrigger>
import {
  Button,
  OverlayArrow,
  Tooltip,
  TooltipTrigger
} from 'react-aria-components';

<TooltipTrigger>
  <Button />
  <Tooltip>
    <OverlayArrow />
  </Tooltip>
</TooltipTrigger>

Accessibility#

Tooltip triggers must be focusable and hoverable in order to ensure that all users can activate them. When displayed, TooltipTrigger automatically associates the tooltip with the trigger element so that it is described by the tooltip content.

Note: tooltips are not shown on touch screen interactions. Ensure that your UI is usable without tooltips, or use an alternative component such as a Popover to show information in an adjacent element.

Reusable wrappers#


If you will use a Tooltip in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps Tooltip and all of its children together into a single component.

import type {TooltipProps} from 'react-aria-components';

interface MyTooltipProps extends Omit<TooltipProps, 'children'> {
  children: React.ReactNode
}

function MyTooltip({children, ...props}: MyTooltipProps) {
  return (
    <Tooltip {...props}>
      <OverlayArrow>
        <svg width={8} height={8}><path d="M0 0,L4 4,L8 0" /></svg>
      </OverlayArrow>
      {children}
    </Tooltip>
  );
}

<TooltipTrigger>
  <Button>💾</Button>
  <MyTooltip>Save</MyTooltip>
</TooltipTrigger>
import type {TooltipProps} from 'react-aria-components';

interface MyTooltipProps
  extends Omit<TooltipProps, 'children'> {
  children: React.ReactNode;
}

function MyTooltip({ children, ...props }: MyTooltipProps) {
  return (
    <Tooltip {...props}>
      <OverlayArrow>
        <svg width={8} height={8}>
          <path d="M0 0,L4 4,L8 0" />
        </svg>
      </OverlayArrow>
      {children}
    </Tooltip>
  );
}

<TooltipTrigger>
  <Button>💾</Button>
  <MyTooltip>Save</MyTooltip>
</TooltipTrigger>
import type {TooltipProps} from 'react-aria-components';

interface MyTooltipProps
  extends
    Omit<
      TooltipProps,
      'children'
    > {
  children:
    React.ReactNode;
}

function MyTooltip(
  { children, ...props }:
    MyTooltipProps
) {
  return (
    <Tooltip {...props}>
      <OverlayArrow>
        <svg
          width={8}
          height={8}
        >
          <path d="M0 0,L4 4,L8 0" />
        </svg>
      </OverlayArrow>
      {children}
    </Tooltip>
  );
}

<TooltipTrigger>
  <Button>💾</Button>
  <MyTooltip>
    Save
  </MyTooltip>
</TooltipTrigger>

Interactions#


Delay#

Tooltips appear after a short delay when hovering the trigger, or instantly when using keyboard focus. This delay is called the "warmup period", and it is a global timer, shared by all tooltips. Once a tooltip is displayed, other tooltips display immediately. If the user waits for the "cooldown period" before hovering another element, the warmup timer restarts.

<div style={{display: 'flex', gap: 8}}>
  <TooltipTrigger>
    <Button>Hover me</Button>
    <MyTooltip>I come up after a delay.</MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>Then hover me</Button>
    <MyTooltip>If you did it quickly, I appear immediately.</MyTooltip>
  </TooltipTrigger>
</div>
<div style={{ display: 'flex', gap: 8 }}>
  <TooltipTrigger>
    <Button>Hover me</Button>
    <MyTooltip>I come up after a delay.</MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>Then hover me</Button>
    <MyTooltip>
      If you did it quickly, I appear immediately.
    </MyTooltip>
  </TooltipTrigger>
</div>
<div
  style={{
    display: 'flex',
    gap: 8
  }}
>
  <TooltipTrigger>
    <Button>
      Hover me
    </Button>
    <MyTooltip>
      I come up after a
      delay.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>
      Then hover me
    </Button>
    <MyTooltip>
      If you did it
      quickly, I appear
      immediately.
    </MyTooltip>
  </TooltipTrigger>
</div>

The delay can be adjusted for hover using the delay prop.

<TooltipTrigger delay={0}>
  <Button>💾</Button>
  <MyTooltip>Save</MyTooltip>
</TooltipTrigger>
<TooltipTrigger delay={0}>
  <Button>💾</Button>
  <MyTooltip>Save</MyTooltip>
</TooltipTrigger>
<TooltipTrigger
  delay={0}
>
  <Button>💾</Button>
  <MyTooltip>
    Save
  </MyTooltip>
</TooltipTrigger>

Trigger#

By default, tooltips appear both on hover and on focus. The trigger prop can be set to "focus" to display the tooltip only on focus, and not on hover.

<TooltipTrigger trigger="focus">
  <Button>💿</Button>
  <MyTooltip>Burn CD</MyTooltip>
</TooltipTrigger>
<TooltipTrigger trigger="focus">
  <Button>💿</Button>
  <MyTooltip>Burn CD</MyTooltip>
</TooltipTrigger>
<TooltipTrigger trigger="focus">
  <Button>💿</Button>
  <MyTooltip>
    Burn CD
  </MyTooltip>
</TooltipTrigger>

Controlled open state#


The open state of the tooltip can be controlled via the defaultOpen and isOpen props.

function Example() {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <TooltipTrigger isOpen={isOpen} onOpenChange={setOpen}>
        <Button>📣</Button>
        <MyTooltip>Notifications</MyTooltip>
      </TooltipTrigger>
      <p>Tooltip is {isOpen ? 'showing' : 'not showing'}</p>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] = React.useState(false);

  return (
    <>
      <TooltipTrigger
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Button>📣</Button>
        <MyTooltip>Notifications</MyTooltip>
      </TooltipTrigger>
      <p>Tooltip is {isOpen ? 'showing' : 'not showing'}</p>
    </>
  );
}
function Example() {
  let [isOpen, setOpen] =
    React.useState(
      false
    );

  return (
    <>
      <TooltipTrigger
        isOpen={isOpen}
        onOpenChange={setOpen}
      >
        <Button>
          📣
        </Button>
        <MyTooltip>
          Notifications
        </MyTooltip>
      </TooltipTrigger>
      <p>
        Tooltip is{' '}
        {isOpen
          ? 'showing'
          : 'not showing'}
      </p>
    </>
  );
}

Positioning#


Placement#

The Tooltip's placement with respect to its trigger element can be adjusted using the placement prop. See the props table for a full list of available placement combinations.

<div style={{ display: 'flex', gap: 8 }}>
  <TooltipTrigger>
    <Button>⬅️</Button>
    <MyTooltip placement="start">
      In left-to-right, this is on the left. In right-to-left, this is on the
      right.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬆️</Button>
    <MyTooltip placement="top">This tooltip is above the button.</MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬇️</Button>
    <MyTooltip placement="bottom">
      This tooltip is below the button.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>➡️</Button>
    <MyTooltip placement="end">
      In left-to-right, this is on the right. In right-to-left, this is on the
      left.
    </MyTooltip>
  </TooltipTrigger>
</div>
<div style={{ display: 'flex', gap: 8 }}>
  <TooltipTrigger>
    <Button>⬅️</Button>
    <MyTooltip placement="start">
      In left-to-right, this is on the left. In
      right-to-left, this is on the right.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬆️</Button>
    <MyTooltip placement="top">
      This tooltip is above the button.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬇️</Button>
    <MyTooltip placement="bottom">
      This tooltip is below the button.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>➡️</Button>
    <MyTooltip placement="end">
      In left-to-right, this is on the right. In
      right-to-left, this is on the left.
    </MyTooltip>
  </TooltipTrigger>
</div>
<div
  style={{
    display: 'flex',
    gap: 8
  }}
>
  <TooltipTrigger>
    <Button>⬅️</Button>
    <MyTooltip placement="start">
      In left-to-right,
      this is on the
      left. In
      right-to-left,
      this is on the
      right.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬆️</Button>
    <MyTooltip placement="top">
      This tooltip is
      above the button.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>⬇️</Button>
    <MyTooltip placement="bottom">
      This tooltip is
      below the button.
    </MyTooltip>
  </TooltipTrigger>
  <TooltipTrigger>
    <Button>➡️</Button>
    <MyTooltip placement="end">
      In left-to-right,
      this is on the
      right. In
      right-to-left,
      this is on the
      left.
    </MyTooltip>
  </TooltipTrigger>
</div>

Offset and cross offset#

The Tooltip's offset with respect to its trigger can be adjusted using the offset and crossOffset props. The offset prop controls the spacing applied along the main axis between the element and its trigger whereas the crossOffset prop handles the spacing applied along the cross axis.

Below is a tooltip offset by an additional 50px above the trigger.

<TooltipTrigger>
  <Button>☝️</Button>
  <MyTooltip offset={50}>This will shift up.</MyTooltip>
</TooltipTrigger>
<TooltipTrigger>
  <Button>☝️</Button>
  <MyTooltip offset={50}>This will shift up.</MyTooltip>
</TooltipTrigger>
<TooltipTrigger>
  <Button>☝️</Button>
  <MyTooltip
    offset={50}
  >
    This will shift up.
  </MyTooltip>
</TooltipTrigger>

Below is a tooltip cross offset by an additional 100px to the right of the trigger.

<TooltipTrigger>
  <Button>👉</Button>
  <MyTooltip crossOffset={60} placement="bottom">
    This will shift over to the right.
  </MyTooltip>
</TooltipTrigger>
<TooltipTrigger>
  <Button>👉</Button>
  <MyTooltip crossOffset={60} placement="bottom">
    This will shift over to the right.
  </MyTooltip>
</TooltipTrigger>
<TooltipTrigger>
  <Button>👉</Button>
  <MyTooltip
    crossOffset={60}
    placement="bottom"
  >
    This will shift
    over to the right.
  </MyTooltip>
</TooltipTrigger>

Disabled#


The isDisabled prop can be provided to a TooltipTrigger to disable the tooltip, without disabling the trigger it displays on.

<TooltipTrigger isDisabled>
  <Button>🖨</Button>
  <MyTooltip>Print</MyTooltip>
</TooltipTrigger>
<TooltipTrigger isDisabled>
  <Button>🖨</Button>
  <MyTooltip>Print</MyTooltip>
</TooltipTrigger>
<TooltipTrigger
  isDisabled
>
  <Button>🖨</Button>
  <MyTooltip>
    Print
  </MyTooltip>
</TooltipTrigger>

Props#


TooltipTrigger#

NameTypeDefaultDescription
childrenReactNode
isDisabledbooleanWhether the tooltip should be disabled, independent from the trigger.
delaynumber1500The delay time for the tooltip to show up. See guidelines.
closeDelaynumber500The delay time for the tooltip to close. See guidelines.
trigger'focus'By default, opens for both focus and hover. Can be made to open only for focus.
isOpenbooleanWhether the overlay is open by default (controlled).
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).
Events
NameTypeDescription
onOpenChange( (isOpen: boolean )) => voidHandler that is called when the overlay's open state changes.

Tooltip#

NameTypeDefaultDescription
triggerRefRefObject<Element>

The ref for the element which the tooltip positions itself with respect to.

When used within a TooltipTrigger this is set automatically. It is only required when used standalone.

placementPlacement'bottom'The placement of the element with respect to its anchor element.
containerPaddingnumber12

The placement padding that should be applied between the element and its surrounding container.

offsetnumber0

The additional offset applied along the main axis between the element and its anchor element.

crossOffsetnumber0

The additional offset applied along the cross axis between the element and its anchor element.

shouldFlipbooleantrue

Whether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely.

isOpenbooleanWhether the element is rendered.
defaultOpenbooleanWhether the overlay is open by default (uncontrolled).
childrenReactNode( (values: TooltipRenderProps )) => ReactNodeThe children of the component. A function may be provided to alter the children based on component state.
classNamestring( (values: TooltipRenderProps )) => stringThe CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: TooltipRenderProps )) => CSSPropertiesThe inline style for the element. A function may be provided to compute the style based on component state.
Events
NameTypeDescription
onOpenChange( (isOpen: boolean )) => voidHandler that is called when the overlay's open state changes.
Accessibility
NameTypeDescription
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

OverlayArrow#

OverlayArrow accepts all HTML attributes.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-Tooltip {
  /* ... */
}
.react-aria-Tooltip {
  /* ... */
}
.react-aria-Tooltip {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<Tooltip className="my-tooltip">
  {/* ... */}
</Tooltip>
<Tooltip className="my-tooltip">
  {/* ... */}
</Tooltip>
<Tooltip className="my-tooltip">
  {/* ... */}
</Tooltip>

In addition, some components support multiple UI states (e.g. focused, placeholder, readonly, etc.). React Aria components expose states using data attributes, which you can target in CSS selectors. For example:

.react-aria-Tooltip[data-placement=left] {
  /* ... */
}
.react-aria-Tooltip[data-placement=left] {
  /* ... */
}
.react-aria-Tooltip[data-placement=left] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<OverlayArrow
  className={({ placement }) =>
    placement === 'left' || placement === 'right' ? 'rotate-90' : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>
<OverlayArrow
  className={({ placement }) =>
    placement === 'left' || placement === 'right'
      ? 'rotate-90'
      : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>
<OverlayArrow
  className={(
    { placement }
  ) =>
    placement ===
        'left' ||
      placement ===
        'right'
      ? 'rotate-90'
      : 'rotate-0'}
>
  {/* ... */}
</OverlayArrow>

Tooltips also support entry and exit animations via states exposed as data attributes and render props. Tooltip will automatically wait for any exit animations to complete before it is removed from the DOM.

.react-aria-Tooltip[data-entering] {
  animation: slide 300ms;
}

.react-aria-Tooltip[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Tooltip[data-entering] {
  animation: slide 300ms;
}

.react-aria-Tooltip[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}
.react-aria-Tooltip[data-entering] {
  animation: slide 300ms;
}

.react-aria-Tooltip[data-exiting] {
  animation: slide 300ms reverse;
}

@keyframes slide {
  /* ... */
}

The states, selectors, and render props for each component used in a TooltipTrigger are documented below.

TooltipTrigger#

The TooltipTrigger component does not render any DOM elements (it only passes through its children) so it does not support styling. If you need a wrapper element, add one yourself inside the <TooltipTrigger>.

<TooltipTrigger>
  <div className="my-tooltip-trigger">
    {/* ... */}
  </div>
</TooltipTrigger>
<TooltipTrigger>
  <div className="my-tooltip-trigger">
    {/* ... */}
  </div>
</TooltipTrigger>
<TooltipTrigger>
  <div className="my-tooltip-trigger">
    {/* ... */}
  </div>
</TooltipTrigger>

Tooltip#

A Tooltip can be targeted with the .react-aria-Tooltip CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
placement[data-placement="left | right | top | bottom"]The placement of the tooltip relative to the trigger.
isEntering[data-entering]Whether the tooltip is currently entering. Use this to apply animations.
isExiting[data-exiting]Whether the tooltip is currently exiting. Use this to apply animations.
stateState of the tooltip.

OverlayArrow#

A OverlayArrow can be targeted with the .react-aria-OverlayArrow CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
placement[data-placement="left | right | top | bottom"]The placement of the overlay relative to the trigger.

Advanced customization#


State#

TooltipTrigger provides an TooltipTriggerState object to its children via TooltipTriggerStateContext. This can be used to access and manipulate the tooltip trigger's state.

Hooks#

If you need to customize things further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See useTooltipTrigger for more details.