Skip to content

Commit 4dff312

Browse files
fix: Tooltip focus issue (#469)
* fix: Tooltip focus issue 1. Fixed issue when Tooltip loses its focus and disappear immediately when its content has interactive elements. 2. Fixed issue when Tooltip does not announce its content when its anchor is not an iteractive element (eg.div) 3. Added A11y section to docs. * add new hasInteractiveContent prop * update changelog
1 parent a0469f2 commit 4dff312

File tree

14 files changed

+218
-21
lines changed

14 files changed

+218
-21
lines changed

apps/docs/docs/components/overlay/Tooltip/_webExamples.mdx

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ You can use tooltips within `TextInput` to provide more context.
8282
<InputLabel htmlFor="tooltip-input-example">Display name</InputLabel>
8383
{/* Add padding to ensure 24x24 tooltip tap target for a11y compliance */}
8484
<Tooltip content="This will be visible to other users.">
85-
<Icon active color="fg" name="info" padding={0.75} size="xs" tabIndex={0} />
85+
<Icon active color="fg" name="info" padding={0.75} size="xs" tabIndex={0} role="button" />
8686
</Tooltip>
8787
</HStack>
8888
}
@@ -111,3 +111,89 @@ function TooltipVisibilityDelay() {
111111
);
112112
}
113113
```
114+
115+
### Accessibility (A11y)
116+
117+
When tooltip content is non-interactive (text only), focus stays on the trigger and the tooltip is rendered in a portal. When tooltip content has **interactive elements** (links, buttons), set **`hasInteractiveContent={true}`** so the tooltip stays in the document flow and keyboard users can tab into the content. This sets `disablePortal`, `disableAutoFocus`, and `disableFocusTrap` appropriately.
118+
119+
**Tooltip on an icon (or other non-button anchor):**
120+
121+
When using an Icon (or other non-`<button>` element) as the tooltip trigger, add **`role="button"`** and **`tabIndex={0}`** so screen readers (e.g. VoiceOver) can discover it with arrow keys and announce the tooltip. If the icon performs an action on click, use **IconButton** instead so the trigger is a real `<button>`.
122+
123+
**Example: tooltip on an icon (string content)**
124+
125+
```jsx live
126+
function TooltipIconStringContent() {
127+
return (
128+
<HStack alignItems="center" gap={2}>
129+
<Tooltip content="This will be visible to other users.">
130+
<Icon active color="fg" name="info" role="button" tabIndex={0} />
131+
</Tooltip>
132+
<Text as="span" font="body" color="fgMuted">
133+
Focus the icon to hear the tooltip announced.
134+
</Text>
135+
</HStack>
136+
);
137+
}
138+
```
139+
140+
**Example: tooltip on an icon (React node content)**
141+
142+
```jsx live
143+
function TooltipIconReactNodeContent() {
144+
return (
145+
<HStack alignItems="center" gap={2}>
146+
<Tooltip
147+
content={
148+
<Text font="label2">
149+
Styled <strong>description</strong> text.
150+
</Text>
151+
}
152+
>
153+
<Icon active color="fg" name="info" role="button" tabIndex={0} />
154+
</Tooltip>
155+
<Text as="span" font="body" color="fgMuted">
156+
Focus the icon to hear the tooltip announced.
157+
</Text>
158+
</HStack>
159+
);
160+
}
161+
```
162+
163+
**When tooltip content is interactive (links, buttons):**
164+
165+
- Prefer **Modal** or another pattern for actionable content when possible. Tooltips are intended for short, non-interactive descriptions.
166+
- If you must use interactive content inside a tooltip, set **`hasInteractiveContent={true}`** so the content stays in the document flow. The prop allows keyboard users to tab into the tooltip, through its content, and out to the next focusable element on the page. With the default portal, focus behavior can be inconsistent when moving between trigger and content.
167+
168+
**Example: tooltip with interactive content**
169+
170+
```jsx live
171+
function TooltipWithInteractiveContent() {
172+
return (
173+
<Box position="relative" paddingY={5}>
174+
<Text as="span" font="body" color="fgMuted">
175+
Set your default display currency.{' '}
176+
</Text>
177+
<Tooltip
178+
content={
179+
<Text font="label2" color="fg">
180+
Learn more at{' '}
181+
<Text
182+
as="a"
183+
href="https://www.coinbase.com/settings"
184+
target="_blank"
185+
rel="noopener noreferrer"
186+
>
187+
Settings
188+
</Text>
189+
.
190+
</Text>
191+
}
192+
hasInteractiveContent
193+
>
194+
<Icon active color="fg" name="info" paddingStart={1} role="button" tabIndex={0} />
195+
</Tooltip>
196+
</Box>
197+
);
198+
}
199+
```

apps/docs/src/theme/Navbar/MobileSidebar/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ export default function NavbarMobileSidebar(): JSX.Element | null {
2020
}, []);
2121

2222
const handleEscPress = useCallback(() => {
23-
mobileSidebar.toggle();
23+
if (mobileSidebar.shown) {
24+
mobileSidebar.toggle();
25+
}
2426
}, [mobileSidebar]);
2527

2628
// Set aria-hidden on main content when sidebar is open

packages/common/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
88

99
<!-- template-start -->
1010

11+
## 8.51.0 ((3/9/2026, 06:39 AM PST))
12+
13+
This is an artificial version bump with no new change.
14+
1115
## 8.50.0 ((3/6/2026, 09:36 AM PST))
1216

1317
This is an artificial version bump with no new change.

packages/common/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coinbase/cds-common",
3-
"version": "8.50.0",
3+
"version": "8.51.0",
44
"description": "Coinbase Design System - Common",
55
"repository": {
66
"type": "git",

packages/mcp-server/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
88

99
<!-- template-start -->
1010

11+
## 8.51.0 ((3/9/2026, 06:39 AM PST))
12+
13+
This is an artificial version bump with no new change.
14+
1115
## 8.50.0 ((3/6/2026, 09:36 AM PST))
1216

1317
This is an artificial version bump with no new change.

packages/mcp-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coinbase/cds-mcp-server",
3-
"version": "8.50.0",
3+
"version": "8.51.0",
44
"description": "Coinbase Design System - MCP Server",
55
"repository": {
66
"type": "git",

packages/mobile/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
88

99
<!-- template-start -->
1010

11+
## 8.51.0 ((3/9/2026, 06:39 AM PST))
12+
13+
This is an artificial version bump with no new change.
14+
1115
## 8.50.0 (3/6/2026 PST)
1216

1317
#### 🚀 Updates

packages/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coinbase/cds-mobile",
3-
"version": "8.50.0",
3+
"version": "8.51.0",
44
"description": "Coinbase Design System - Mobile",
55
"repository": {
66
"type": "git",

packages/web/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file.
88

99
<!-- template-start -->
1010

11+
## 8.51.0 (3/9/2026 PST)
12+
13+
#### 🚀 Updates
14+
15+
- Added hasInteractiveContent prop to Tooltip to correctly handle keyboard navigation when content includes interactive elements. [[#469](https://github.com/coinbase/cds/pull/469)] [DX-5097]
16+
17+
#### 🐞 Fixes
18+
19+
- Fixed issue when tooltip does not announce its content when content is a React Node instead of a string. [[#469](https://github.com/coinbase/cds/pull/469)]
20+
1121
## 8.50.0 (3/6/2026 PST)
1222

1323
#### 🚀 Updates

packages/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@coinbase/cds-web",
3-
"version": "8.50.0",
3+
"version": "8.51.0",
44
"description": "Coinbase Design System - Web",
55
"repository": {
66
"type": "git",

0 commit comments

Comments
 (0)