Skip to content

Commit

Permalink
Avoid chaining aria-labelledby on tooltips
Browse files Browse the repository at this point in the history
aria-labelledby cannot be chained (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby#benefits_and_drawbacks). So, it's not good practice to be setting aria-labelledby on both the anchor and the tooltip element.

The basic issue that I believe initially motivated the chaining was to distinguish the caption from the main tooltip content (by setting both aria-labelledby and aria-describedby on the tooltip element), so that the caption would not be considered part of the anchor's accessible label. But this was never actually working. The only good solution seems to be setting aria-labelledby and aria-describedby on the anchor to point directly to the label and caption elements.
  • Loading branch information
robintown committed Oct 1, 2024
1 parent 7181573 commit 72aa36a
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exports[`EditInPlace > disables control when disabled 1`] = `
>
<label
class="_label_dc87d2"
for="radix-:r7m:"
for="radix-:r6o:"
>
Edit Me
</label>
Expand All @@ -20,7 +20,7 @@ exports[`EditInPlace > disables control when disabled 1`] = `
<input
class="_control_a839aa"
disabled=""
id="radix-:r7m:"
id="radix-:r6o:"
name="input"
title=""
/>
Expand All @@ -40,7 +40,7 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 1`] = `
>
<label
class="_label_dc87d2"
for="radix-:r61:"
for="radix-:r59:"
>
Edit Me
</label>
Expand All @@ -49,7 +49,7 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 1`] = `
>
<input
class="_control_a839aa"
id="radix-:r61:"
id="radix-:r59:"
name="input"
title=""
/>
Expand All @@ -71,19 +71,19 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 2`] = `
<label
class="_label_dc87d2"
data-valid="true"
for="radix-:r61:"
for="radix-:r59:"
>
Edit Me
</label>
<div
class="_controls_980156"
>
<input
aria-describedby="radix-:r6g:"
aria-describedby="radix-:r5m:"
class="_control_a839aa"
data-valid="true"
disabled=""
id="radix-:r61:"
id="radix-:r59:"
name="input"
title=""
/>
Expand All @@ -92,7 +92,7 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 2`] = `
>
<button
aria-disabled="true"
aria-labelledby=":r62:"
aria-labelledby=":r5a:"
class="_button_2a1efe _has-icon_2a1efe _icon-only_2a1efe"
data-kind="primary"
data-size="sm"
Expand All @@ -116,7 +116,7 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 2`] = `
</button>
<button
aria-disabled="true"
aria-labelledby=":r68:"
aria-labelledby=":r5f:"
class="_button_2a1efe _button_980156 _has-icon_2a1efe _icon-only_2a1efe"
data-kind="secondary"
data-size="sm"
Expand All @@ -142,7 +142,7 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 2`] = `
</div>
<span
class="_message_dc87d2"
id="radix-:r6g:"
id="radix-:r5m:"
>
<svg
class="_icon_01cd2d"
Expand Down Expand Up @@ -178,25 +178,25 @@ exports[`EditInPlace > displays saved label for 2 seconds after save 3`] = `
<label
class="_label_dc87d2"
data-valid="true"
for="radix-:r61:"
for="radix-:r59:"
>
Edit Me
</label>
<div
class="_controls_980156"
>
<input
aria-describedby="radix-:r6h:"
aria-describedby="radix-:r5n:"
class="_control_a839aa"
data-valid="true"
id="radix-:r61:"
id="radix-:r59:"
name="input"
title=""
/>
</div>
<span
class="_message_dc87d2 _success-message_dc87d2"
id="radix-:r6h:"
id="radix-:r5n:"
>
<svg
fill="currentColor"
Expand Down Expand Up @@ -257,19 +257,19 @@ exports[`EditInPlace > renders error icon and text if passed as children 1`] = `
<label
class="_label_dc87d2"
data-invalid="true"
for="radix-:rh:"
for="radix-:rf:"
>
Edit Me
</label>
<div
class="_controls_980156"
>
<input
aria-describedby="radix-:ri:"
aria-describedby="radix-:rg:"
aria-invalid="true"
class="_control_a839aa"
data-invalid="true"
id="radix-:rh:"
id="radix-:rf:"
name="input"
title=""
/>
Expand All @@ -278,7 +278,7 @@ exports[`EditInPlace > renders error icon and text if passed as children 1`] = `
>
<button
aria-disabled="true"
aria-labelledby=":rj:"
aria-labelledby=":rh:"
class="_button_2a1efe _has-icon_2a1efe _icon-only_2a1efe"
data-kind="primary"
data-size="sm"
Expand All @@ -302,7 +302,7 @@ exports[`EditInPlace > renders error icon and text if passed as children 1`] = `
</button>
<button
aria-disabled="false"
aria-labelledby=":rp:"
aria-labelledby=":rm:"
class="_button_2a1efe _button_980156 _has-icon_2a1efe _icon-only_2a1efe"
data-kind="secondary"
data-size="sm"
Expand All @@ -328,7 +328,7 @@ exports[`EditInPlace > renders error icon and text if passed as children 1`] = `
</div>
<span
class="_message_dc87d2 _error-message_dc87d2"
id="radix-:ri:"
id="radix-:rg:"
>
<svg
fill="currentColor"
Expand Down Expand Up @@ -358,7 +358,7 @@ exports[`EditInPlace > uses native form validation logic 1`] = `
>
<label
class="_label_dc87d2"
for="radix-:r11:"
for="radix-:rt:"
>
Edit Me
</label>
Expand All @@ -367,7 +367,7 @@ exports[`EditInPlace > uses native form validation logic 1`] = `
>
<input
class="_control_a839aa"
id="radix-:r11:"
id="radix-:rt:"
name="input"
required=""
title=""
Expand All @@ -389,7 +389,7 @@ exports[`EditInPlace > uses the custom error messages passed as children 1`] = `
>
<label
class="_label_dc87d2"
for="radix-:r1i:"
for="radix-:r1c:"
>
Edit Me
</label>
Expand All @@ -398,7 +398,7 @@ exports[`EditInPlace > uses the custom error messages passed as children 1`] = `
>
<input
class="_control_a839aa"
id="radix-:r1i:"
id="radix-:r1c:"
name="input"
title=""
/>
Expand Down
14 changes: 8 additions & 6 deletions src/components/Tooltip/Tooltip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,20 @@ describe("Tooltip", () => {
const user = userEvent.setup();
render(<WithStringCaption />);
await user.tab();
// tooltip labels button, opens, and displays caption
screen.getByRole("button", { name: /I can have a caption/ });
screen.getByText("My beautiful caption");
// tooltip labels button and describes button with caption
expect(
screen.getByRole("button", { name: "I can have a caption" }),
).toHaveAccessibleDescription("My beautiful caption");
});

it("renders with component caption", async () => {
const user = userEvent.setup();
render(<WithComponentCaption />);
await user.tab();
// tooltip labels button, opens, and displays caption
screen.getByRole("button", { name: /Copy/ });
screen.getByText("Ctrl");
// tooltip labels button and describes button with caption
expect(
screen.getByRole("button", { name: "Copy" }),
).toHaveAccessibleDescription("Ctrl + C");
});

it("renders a descriptive tooltip", async () => {
Expand Down
2 changes: 0 additions & 2 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ function TooltipContent({
<FloatingPortal>
<div
ref={rest.refs.setFloating}
aria-labelledby={rest.labelId}
aria-describedby={rest.captionId || rest.labelId}
style={rest.floatingStyles}
{...rest.getFloatingProps()}
className={classNames(styles.tooltip, {
Expand Down
13 changes: 5 additions & 8 deletions src/components/Tooltip/useTooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@ export function useTooltip({
caption,
...props
}: UseTooltipProps) {
const contentId = useId();
// Set on `aria-labelledby` attribute of the tooltip content
const labelId = useId();
// Set on `aria-describedby` attribute of the tooltip content
const captionId = useId();
const arrowRef = useRef(null);

Expand Down Expand Up @@ -184,19 +181,19 @@ export function useTooltip({
purpose === "label"
? {
// The props we want to set on the anchor element
reference: { "aria-labelledby": contentId },
// The props we want to set on the content element
floating: { id: contentId },
reference: {
"aria-labelledby": labelId,
"aria-describedby": caption ? captionId : undefined,
},
}
: {},
[purpose, contentId],
[purpose],
);

const interactions = useInteractions([hover, focus, dismiss, role, label]);

return useMemo(
() => ({
contentId,
labelId,
captionId: caption ? captionId : undefined,
caption,
Expand Down

0 comments on commit 72aa36a

Please sign in to comment.