Tooltip

A tooltip briefly describes an interactive element on mouse hover or keyboard focus.

Default

Use the default tooltip to display brief, helpful information when a person hovers over a target element. The default tooltip will:

  • appear and disappear after a short delay.
  • remain visible if someone briefly moves their mouse off and back onto the target.
  • disappear immediately if someone hovers over or focuses on another element with a tooltip, or if they scroll the page.

Tooltip content will wrap at 240px to maintain readability.

import React from 'react'; import Button from '@atlaskit/button/default/button'; import { Inline } from '@atlaskit/primitives/compiled/inline'; import Tooltip from '@atlaskit/tooltip/Tooltip'; export default function TooltipDefaultExample(): React.JSX.Element { return ( <Inline space="space.100"> <Tooltip content="This is a tooltip"> {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Single line example </Button> )} </Tooltip> <Tooltip content="This is a tooltip with a longer message that will wrap at 240px to maintain readability."> {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Multi-line example </Button> )} </Tooltip> </Inline> ); }

With keyboard shortcut

Use the shortcut prop to display a keyboard shortcut in the tooltip. Keys will be displayed as individual keyboard key segments below the tooltip content.

Keyboard shortcuts in tooltips are hidden from screen readers, so always provide them in another way, such as a panel or dialog. This ensures everyone can discover and use shortcuts, no matter how they navigate.

import React from 'react'; import Button from '@atlaskit/button/default/button'; import { Inline } from '@atlaskit/primitives/compiled/inline'; import Tooltip from '@atlaskit/tooltip/Tooltip'; export default function TooltipKeyboardShortcutExample(): React.JSX.Element { return ( <Inline space="space.100"> <Tooltip content="This is a tooltip" shortcut={['Ctrl', '[']}> {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Single line example </Button> )} </Tooltip> <Tooltip content="This is a tooltip with a longer message that will wrap at 240px to maintain readability." shortcut={['Ctrl', '[']} > {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Multi-line example </Button> )} </Tooltip> </Inline> ); }

Positioning

Relative to target

Use the position prop to specify where the tooltip appears relative to its target: top, right, left, or bottom.

  • Set position="auto" so the tooltip automatically shows on the side with the most available space.
  • If you don’t set a position, the tooltip defaults to bottom.
  • If the preferred side would cause it to overflow the screen, the tooltip will automatically adjust its position.
import React from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap } from '@atlaskit/css'; import { placements } from '@atlaskit/popper/main'; import { Box } from '@atlaskit/primitives/compiled/box'; import { token } from '@atlaskit/tokens'; import Tooltip from '@atlaskit/tooltip/Tooltip'; const placementGridPositions = cssMap({ 'top-start': { gridColumn: 2, gridRow: 1, }, top: { gridColumn: 3, gridRow: 1, }, 'top-end': { gridColumn: 4, gridRow: 1, }, 'bottom-start': { gridColumn: 2, gridRow: 5, }, bottom: { gridColumn: 3, gridRow: 5, }, 'bottom-end': { gridColumn: 4, gridRow: 5, }, 'right-start': { gridColumn: 5, gridRow: 2, }, right: { gridColumn: 5, gridRow: 3, }, 'right-end': { gridColumn: 5, gridRow: 4, }, 'left-start': { gridColumn: 1, gridRow: 2, }, left: { gridColumn: 1, gridRow: 3, }, 'left-end': { gridColumn: 1, gridRow: 4, }, 'auto-start': { gridColumn: 3, gridRow: 2, }, auto: { gridColumn: 3, gridRow: 3, }, 'auto-end': { gridColumn: 3, gridRow: 4, }, }); const buttonGridStyles = cssMap({ root: { display: 'grid', gap: token('space.100'), gridTemplate: 'repeat(5, 1fr) / repeat(5, 1fr)', justifyItems: 'stretch', }, }); const PositionExample = (): React.JSX.Element => { return ( <Box xcss={buttonGridStyles.root}> {placements.map((placement) => ( <Box key={placement} xcss={placementGridPositions[placement]}> <Tooltip position={placement} content={placement}> {(tooltipProps) => ( <Button {...tooltipProps} shouldFitContainer> {placement} </Button> )} </Tooltip> </Box> ))} </Box> ); }; export default PositionExample;

Relative to mouse pointer

Positioning the tooltip near the mouse pointer, rather than the target, is helpful in scenarios where the tooltip may be visually disconnected from where someone hovers.

Examples include hovering on large target areas, such as panel resizers, or when someone hovers on an element while zoomed in on part of the screen where the tooltip doesn’t show.

  • To display the tooltip next to the mouse pointer (instead of the target element), set position="mouse".
  • Further adjust the tooltip’s placement relative to the mouse by using the mousePosition property.
  • For keyboard users, the tooltip will always appear below (bottom position) the target element by default.
Resize me! Hover or focus on the right edge
import { type CSSProperties, useRef, useState } from 'react'; import { cssMap, jsx } from '@compiled/react'; import { PanelSplitter, PanelSplitterProvider, type ResizeBounds, } from '@atlaskit/navigation-system/layout/panel-splitter'; import { token } from '@atlaskit/tokens'; const widthVar = '--panel-width'; const resizingCssVar = '--panel-splitter-resizing'; const styles = cssMap({ root: { width: `var(${resizingCssVar}, var(${widthVar}))`, height: '200px', position: 'relative', backgroundColor: token('color.background.accent.gray.subtlest'), borderInlineEnd: `${token('border.width')} solid ${token('color.border')}`, paddingBlockStart: token('space.100'), paddingInlineEnd: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.100'), }, }); function getResizeBounds(): ResizeBounds { return { min: '150px', max: '400px' }; } const PanelSplitterWithTooltipAndShortcut = (): JSX.Element => { const panelSplitterParentRef = useRef<HTMLDivElement | null>(null); const [width, setWidth] = useState(300); return ( <div ref={panelSplitterParentRef} css={styles.root} style={ { [widthVar]: `${width}px`, } as CSSProperties } > Resize me! Hover or focus on the right edge <br /> <PanelSplitterProvider panelRef={panelSplitterParentRef} panelWidth={width} onCompleteResize={setWidth} getResizeBounds={getResizeBounds} resizingCssVar={resizingCssVar} position="end" shortcut={['Ctrl', '[']} > <PanelSplitter label="Resize panel" testId="panel-splitter" tooltipContent="Collapse panel" /> </PanelSplitterProvider> </div> ); }; export default PanelSplitterWithTooltipAndShortcut;

Updating tooltip position

When the content of a tooltip changes due to lazy loading, its position isn't recalculated and the tooltip may become misaligned.

If you need the tooltip to recalculate its position, you can control this manually using the update callback provided to the content render prop.

import React, { type ReactNode, useEffect, useLayoutEffect, useState } from 'react'; import Button from '@atlaskit/button/default/button'; import { Inline } from '@atlaskit/primitives/compiled/inline'; import Tooltip from '@atlaskit/tooltip/Tooltip'; /** * Content updates after a timeout only (no click). * Example was changed from click-to-toggle so that with top-layer (popover="hint"), testing * doesn't trigger light-dismiss: clicking outside the tooltip closes it, which made the * update example appear broken. Hover + wait for timeout avoids that. */ const CONTENT_UPDATE_DELAY_MS = 2000; function TooltipContent({ update }: { update?: () => void }): ReactNode { const [isLoading, setIsLoading] = useState(true); useEffect(() => { const id = setTimeout(() => { setIsLoading(false); }, CONTENT_UPDATE_DELAY_MS); return () => clearTimeout(id); }, []); useLayoutEffect(() => { update?.(); }, [isLoading, update]); return isLoading ? 'Loading...' : 'I am a lazy loaded tooltip, with a lot of content'; } export default function TooltipUpdateContentExample(): React.JSX.Element { return ( <Inline space="space.100"> <Tooltip content={({ update }) => <TooltipContent update={update} />}> {(tooltipProps) => ( <Button {...tooltipProps}> Hover and wait — content updates after {CONTENT_UPDATE_DELAY_MS / 1000}s </Button> )} </Tooltip> <Tooltip content={() => <TooltipContent />}> {(tooltipProps) => <Button {...tooltipProps}>Not using the update callback</Button>} </Tooltip> </Inline> ); }

Conditional tooltips for truncation

A tooltip can be conditionally shown by leveraging the canAppear prop. Use when you want the tooltip to only show when the element content is truncated.

import { useRef } from 'react'; import { jsx } from '@compiled/react'; import invariant from 'tiny-invariant'; import { cssMap, cx } from '@atlaskit/css'; import { Pressable } from '@atlaskit/primitives/compiled/pressable'; import { Stack } from '@atlaskit/primitives/compiled/stack'; import { Text } from '@atlaskit/primitives/compiled/text'; import { token } from '@atlaskit/tokens'; import Tooltip from '@atlaskit/tooltip/Tooltip'; const styles = cssMap({ root: { paddingBlockStart: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.100'), paddingInlineEnd: token('space.100'), borderColor: token('color.border'), borderRadius: token('radius.small'), borderStyle: 'solid', borderWidth: token('border.width'), backgroundColor: token('elevation.surface'), textAlign: 'start', '&:hover': { backgroundColor: token('elevation.surface.hovered'), }, '&:active': { backgroundColor: token('elevation.surface.pressed'), }, }, }); const smallStyles = cssMap({ root: { width: '200px', }, }); const content = { first: 'Tooltip shown on this item as it is concatenated', second: 'No tooltip shown as this item is not being concatenated', }; export default function Example(): JSX.Element { const firstRef = useRef<HTMLElement | null>(null); const secondRef = useRef<HTMLElement | null>(null); return ( <Stack space="space.100"> <Tooltip content={content.first} // don't need a screen reader announcement as the // tooltip content is the same as the items content isScreenReaderAnnouncementDisabled canAppear={() => { const element = firstRef.current; invariant(element); // Only showing the tooltip for this item when // the element has been clamped. return element.scrollHeight > element.clientHeight; }} > {(props) => ( <Pressable {...props} xcss={cx(styles.root, smallStyles.root)}> <Text ref={firstRef} maxLines={1}> {content.first} </Text> </Pressable> )} </Tooltip> <Tooltip content={content.second} // don't need a screen reader announcement as the // tooltip content is the same as the items content canAppear={() => { const element = secondRef.current; invariant(element); // Only showing the tooltip for this item when // the element has been clamped. return element.scrollHeight > element.clientHeight; }} > {(props) => ( <Pressable {...props} xcss={styles.root}> <Text ref={secondRef} maxLines={1}> {content.second} </Text> </Pressable> )} </Tooltip> </Stack> ); }

Ignoring pointer events

In some cases, a tooltip can get in the way if it stays visible when you move your mouse over it, making it hard to interact with elements underneath or nearby.

To avoid this, set the ignoreTooltipPointerEvents prop to true. This makes the tooltip ignore mouse interactions, so users can easily interact with other elements on the page. This works by applying pointer-events: none to the tooltip.

Default tooltip

Tooltip ignoring pointer events

import React from 'react'; import Button from '@atlaskit/button/default/button'; import { Inline } from '@atlaskit/primitives/compiled/inline'; import { Stack } from '@atlaskit/primitives/compiled/stack'; import Tooltip from '@atlaskit/tooltip/Tooltip'; export default function TooltipPreventInteractionsExample(): React.JSX.Element { return ( <Stack space="space.100"> <Stack space="space.100"> <p>Default tooltip</p> <Inline space="space.100"> <Tooltip content="This is a tooltip" position="right"> {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Hover me first </Button> )} </Tooltip> <Button>Hover me second</Button> </Inline> </Stack> <Stack space="space.100"> <p>Tooltip ignoring pointer events</p> <Inline space="space.100"> <Tooltip content="This is a tooltip" position="right" ignoreTooltipPointerEvents> {(tooltipProps) => ( <Button appearance="primary" {...tooltipProps}> Hover me first </Button> )} </Tooltip> <Button>Hover me second</Button> </Inline> </Stack> </Stack> ); }

Customizing tooltip

Use the component prop to customize the look and feel of the tooltip. The TooltipPrimitive component can be used as a base.

Never put links or other interactive elements in tooltips. Tooltips are not accessible to keyboard navigation, so users cannot tab into or interact with these elements, making them inaccessible.

import { forwardRef } from 'react'; import Button from '@atlaskit/button/default/button'; import { cssMap, jsx } from '@atlaskit/css'; import { token } from '@atlaskit/tokens'; import Tooltip from '@atlaskit/tooltip/Tooltip'; import TooltipPrimitive, { type TooltipPrimitiveProps } from '@atlaskit/tooltip/TooltipPrimitive'; const styles = cssMap({ root: { backgroundColor: token('elevation.surface'), borderRadius: token('radius.small'), boxShadow: token('elevation.shadow.overlay'), color: token('color.text'), maxHeight: '300px', maxWidth: '300px', paddingBlockStart: token('space.100'), paddingBlockEnd: token('space.100'), paddingInlineStart: token('space.150'), paddingInlineEnd: token('space.150'), }, }); const CustomTooltip: React.ForwardRefExoticComponent< React.PropsWithoutRef<TooltipPrimitiveProps> & React.RefAttributes<HTMLDivElement> > = forwardRef<HTMLDivElement, TooltipPrimitiveProps>(function CustomTooltip( { children, className, ...rest }, ref, ) { return ( <TooltipPrimitive {...rest} // Manually passing on `className` so it gets merged correctly in the build output. // The passed classname is mostly used for integration testing (`.Tooltip`) className={className} // "css" does not "exist" - it gets transformed into "className" by compiled css={styles.root} ref={ref} > {children} </TooltipPrimitive> ); }); export default function TooltipCustomizationExample(): JSX.Element { return ( <Tooltip component={CustomTooltip} content="This is a customized tooltip"> {(tooltipProps) => <Button {...tooltipProps}>Hover or keyboard focus on me</Button>} </Tooltip> ); }

Accessibility

Screen reader support

To make tooltip content accessible to screen readers, the tooltip component creates a hidden element containing the tooltip’s content. The tooltip trigger (the child of <Tooltip>) receives an aria-describedby attribute that links it to this hidden element.

  • If content is a function: aria-describedby is provided as a prop.
  • If content is a component: aria-describedby is added to the element in a useEffect.

To prevent the hidden element from being used for screen reader announcements, set the isScreenReaderAnnouncementDisabled prop. This is helpful when the tooltip content is the same as the trigger content, so no hidden element is needed.

Because keyboard shortcuts in tooltips are hidden from assistive technologies, always provide them in another way, such as in a panel or dialog. This ensures everyone can discover and use shortcuts, no matter how they navigate.

Never use the title attribute

Don’t use the HTML title attribute on any children of the tooltip component. Using title can cause double tooltips to appear and creates accessibility issues. The title attribute is not reliably supported by screen readers, and it is inaccessible to keyboard-only and mobile users.

import React from 'react'; import Button from '@atlaskit/button/default/button'; import Tooltip from '@atlaskit/tooltip/Tooltip'; export default (): React.JSX.Element => ( <Tooltip content="Never use the title attribute. Double tooltips will be displayed." position="right" > {(tooltipProps) => ( <Button appearance="primary" title="This is a native tooltip from the title attribute. Don't do this, it isn't accessible." {...tooltipProps} > Hover to reveal my tooltip and title attribute </Button> )} </Tooltip> );

Never put tooltips on disabled elements

Tooltips should only appear on interactive elements. Disabled elements can’t be reached by all devices or assistive technologies, making their tooltips inaccessible.

Tooltips on disabled elements can also confuse users, disrupt navigation, and are difficult to use on touch devices or with zoom magnification. People may not expect to find information by hovering over disabled or non-interactive elements.

See button guidance for information on avoiding disabled buttons.

Was this page helpful?
We use this feedback to improve our documentation.
© 2026 AtlassianTrademark, (opens new window)Privacy, (opens new window)License