Usage
import { Tooltip } from '@loomhq/lens'
<Tooltip content="Non-natively focusable child" placement='topCenter'>
<Text color="orange" fontWeight="bold" isInline>
Hover or focus me
</Text>
</Tooltip>
Accessibility
Please verify that your component supports the following accessibility features that come out of box and none of your modifications negate these features.
Tooltip
should:
- Have a clear trigger to open: Natively-focusable element (e.g.,
<Button type="button" ...>
) or non-natively focusable element (e.g., <Text...>
)
- Be renderable and togglable (open and closeable) by every input device (mouse, keyboard, switch, etc).
- Be helpers, not store critical information holders. Ask: If the user cannot access the
Tooltip
, are we inequitably hiding a critical piece of information that could impact their billing, for example, or their ability to use Loom? If yes, then render that information elsewhere more widely accessible.
Open:
- Upon
hover
of its trigger
- Upon
focus
of its trigger
Close:
- Upon
hover
of its trigger
- Upon
blur
of its trigger
Screenreader support
Screenreader support is still [WIP]. In-code comments outlines the remaining work with Linear ticket references.
Gotchas
-
Define tabIndex={-1}
for the tooltip if your children have focusable elements. See Examples.
-
The Tooltip will not render if content={null}
. Use case is typically to conditionally render the tooltip. Otherwise, if it's always null, consider removing the tooltip altgother.
-
focus
and blur
do not bubble, so if you have multiple nested layers of focusable elements within your Tooltip
, you may experience issues. Recommendation is to always wrap your Tooltip at the lowest, most precise level around the trigger element and avoid nesting focusable items.
Placement
<Arrange gap="medium" columns="repeat(3, 1fr)">
{[
'topLeft',
'topCenter',
'topRight',
'bottomLeft',
'bottomCenter',
'bottomRight',
'leftTop',
'leftCenter',
'leftBottom',
'rightTop',
'rightCenter',
'rightBottom',
].map((placement, index) => (
<Tooltip content="I'm here" placement={placement} key={index}>
<Container backgroundColor="overlay" borderSide="all" padding="medium">
<Text alignment="center">{placement}</Text>
</Container>
</Tooltip>
))}
</Arrange>
Max width
<Tooltip maxWidth={40} content={demoText.medium}>
<div>trigger</div>
</Tooltip>
Trigger offset
<Arrange gap="large">
<Tooltip content='4px' triggerOffset={4}>
<div>Offset from trigger 4px</div>
</Tooltip>
<Tooltip content='32px' triggerOffset={32}>
<div>Offset from trigger 32px</div>
</Tooltip>
</Arrange>
Keep open
When keepOpen
is true
the Tooltip will stay open when it's hovered. By default, Tooltips stay open when they are keyboard focused.
<Tooltip keepOpen content='Hover me and I will stay open'>
<div>Keep my tooltip open</div>
</Tooltip>
Disabled
When isDisabled
is true
the Tooltip will never show.
() => {
const [isDisabled, setDisabled] = React.useState(true)
return (
<>
<Tooltip isDisabled={isDisabled} content='Tooltip is enabled'>
<div>Hover or focus to test the tooltip</div>
</Tooltip>
<div>
<Button onClick={() => setDisabled(s => !s)} variant="primary">
{isDisabled ? 'Enable' : 'Disable'}
</Button>
</div>
</>
)}
Inline and block
<>
<Tooltip isInline content='Content'>
<Container borderSide="all" borderColor="red">Wrap children with display: inline-block</Container>
</Tooltip>
<Tooltip isInline={false} content='Content'>
<Container borderSide="all" borderColor="red">Wrap children with display: block</Container>
</Tooltip>
</>
Delay
Sets a delay on when to show the tooltip upon hover. Note that there is no delay for focus behaviour, which is by default immediate.
<Arrange gap="medium">
<Tooltip content='Content'>
<Container backgroundColor="overlay" borderSide="all" padding="medium">
<Text alignment="center">Immediate (default)</Text>
</Container>
</Tooltip>
<Tooltip content='Content' delay="long">
<Container backgroundColor="overlay" borderSide="all" padding="medium">
<Text alignment="center">Long Delay (800ms)</Text>
</Container>
</Tooltip>
</Arrange>
With shortcut
Tooltips with keyboard shortcuts
<Arrange gap="small">
<Tooltip content="Play" shortcut={['K']} tabIndex={-1}>
<IconButton altText="Play" icon={<SvgPlay />} />
</Tooltip>
<Tooltip content="New" shortcut={['⌘', 'N']} tabIndex={-1}>
<IconButton altText="New" icon={<SvgAdd />} />
</Tooltip>
<Tooltip content="Transcript" shortcut={['shift', 'T']} tabIndex={-1}>
<IconButton altText="Transcript" icon={<SvgNotes />} />
</Tooltip>
</Arrange>
Container
Specify where it's going to be rendered in the DOM, this is especially useful when working with Shadow DOM.
<Tooltip content="Content" container={() => document.querySelector("#renderContainer")}>
Trigger
</Tooltip>
Tooltip uses TooltipBox
internally, use it to build a tooltip with custom behavior.
<TooltipBox maxWidth={30}>
TooltipBox content
</TooltipBox>
Examples
In order to prevent nested tabbing issues, make sure you assign tabIndex={-1}
if you have any natively focusable children. Your natively focusable children will maintain their native tabIndeces
and should receive the same tab order as without the Tooltip.
<>
<Tooltip tabIndex={-1} content='Delete'>
<IconButton icon={<SvgTrash />} altText="Trash"/>
</Tooltip>
<Tooltip tabIndex={-1} content='Add'>
<IconButton icon={<SvgAdd />} altText="Add"/>
</Tooltip>
<Tooltip tabIndex={-1} content='Close'>
<IconButton icon={<SvgClose />} altText="Close"/>
</Tooltip>
</>
Props
placement
'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight'
| 'leftTop'
| 'leftCenter'
| 'leftBottom'
| 'rightTop'
| 'rightCenter'
| 'rightBottom'
'topCenter'
maxWidth
number | string | []
26
verticalAlign
string
'middle'
delay
'immediate' | 'long'
'immediate'
container
HTMLElement | (() => HTMLElement) | string
maxWidth
number | string | []
onMouseEnter
React.ReactEventHandler
onMouseLeave
React.ReactEventHandler