Accessibility traversal order of tooltip changed
Summary
#During accessibility focus traversal, Tooltip.message
is visited immediately after Tooltip.child
.
Background
#The Tooltip
widget usually wraps an interactive UI component such as a button, and shows a help message when long pressed. When the message is visible, assistive technologies should announce it after the button.
The Tooltip
widget originally put Tooltip.message
on an OverlayEntry
when long pressed. As a result, Tooltip.message
was not immediately after Tooltip.child
in the semantics tree.
Migration guide
#This change moved the tooltip message in the semantics tree. You might see accessibility test failures if your tests expect a tooltip message to appear in a specific location in the semantics tree, when it is visible. Update any failing accessibility tests to adopt the new tooltip semantics order.
For example, if you constructed the following widget tree in your test:
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) {
return ListView(
children: <Widget>[
const Text('before'),
Tooltip(
key: tooltipKey,
showDuration: const Duration(days: 365),
message: 'message',
child: const Text('child'),
),
const Text('after'),
],
);
},
),
],
),
);
When the tooltip message is visible, the corresponding semantics tree before this change should look like this:
SemanticsNode#0
│
├─SemanticsNode#1
│ │
│ └─SemanticsNode#5
│ │ flags: hasImplicitScrolling
│ │ scrollChildren: 3
│ │
│ ├─SemanticsNode#2
│ │ tags: RenderViewport.twoPane
│ │ label: "before"
│ │ textDirection: ltr
│ │
│ ├─SemanticsNode#3
│ │ tags: RenderViewport.twoPane
│ │ label: "child"
│ │ tooltip: "message"
│ │ textDirection: ltr
│ │
│ └─SemanticsNode#4
│ tags: RenderViewport.twoPane
│ label: "after"
│ textDirection: ltr
│
└─SemanticsNode#6
label: "message"
textDirection: ltr
After this change, the same widget tree generates a slightly different semantics tree, as shown below. Node #6 becomes a child of node #3, instead of node #0.
SemanticsNode#0
│
└─SemanticsNode#1
│
└─SemanticsNode#5
│ flags: hasImplicitScrolling
│ scrollChildren: 3
│
├─SemanticsNode#2
│ tags: RenderViewport.twoPane
│ label: "before"
│ textDirection: ltr
│
├─SemanticsNode#3
│ │ tags: RenderViewport.twoPane
│ │ label: "child"
│ │ tooltip: "message"
│ │ textDirection: ltr
│ │
│ └─SemanticsNode#6
│ label: "message"
│ textDirection: ltr
│
└─SemanticsNode#4
tags: RenderViewport.twoPane
label: "after"
textDirection: ltr
Timeline
#Landed in version: 3.16.0-11.0.pre
In stable release: 3.19.0
References
#API documentation:
Relevant PRs:
Unless stated otherwise, the documentation on this site reflects the latest stable version of Flutter. Page last updated on 2024-04-04. View source or report an issue.