Skip to content

Fix Tooltip losing its semantics label when TooltipVisibility.visible is false - #12898

Open
hkarmoush wants to merge 4 commits into
flutter:mainfrom
hkarmoush:fix/189062-tooltip-visibility-semantics
Open

hkarmoush wants to merge 4 commits into
flutter:mainfrom
hkarmoush:fix/189062-tooltip-visibility-semantics

Conversation

@hkarmoush

Copy link
Copy Markdown

Ports flutter/flutter#189362 to material_ui now that the Material/Cupertino code freeze has lifted, per the instructions in flutter/flutter#188444.

Original PR description:


TooltipVisibility(visible: false) is the idiomatic way to suppress a
tooltip's visual overlay — most commonly done when an accessibility
service is active, because the floating tooltip is visual noise for a
screen reader user. The expectation is that the visual tooltip goes away
but the semantic label survives, so e.g. an IconButton(tooltip: 'Add')
still announces "Add" to TalkBack/VoiceOver.

Before this change, the opposite happened: an IconButton wrapped in
TooltipVisibility(visible: false) announced only "button" — the
accessibility label was dropped entirely.

Root cause

In _TooltipState.build(), the Semantics(tooltip: ...) node was only
ever attached inside RawTooltip, and RawTooltip was only built when
the tooltip was visible, coupling the accessibility label to the
interactive overlay machinery it shouldn't depend on.

Fix

Add an else if (!excludeFromSemantics) branch that still attaches a
plain Semantics(tooltip: _tooltipMessage, ...) wrapper when not
visible, mirroring the same construct RawTooltip itself uses, without
any of the gesture/overlay wrappers.

excludeFromSemantics opt-out behavior is preserved in both branches.

Tests

Added tests to tooltip_visibility_test.dart:

  • The tooltip message is still exposed as a semantics tooltip label
    when TooltipVisibility(visible: false).
  • The label is still correctly suppressed when excludeFromSemantics: true, even while not visible.
  • A regression test for IconButton.tooltip under
    TooltipVisibility(visible: false).

Fixes flutter/flutter#189062

Closes flutter/flutter#189362 (superseded by this PR)

… is false

TooltipVisibility(visible: false) is meant to suppress only the visual
tooltip overlay (e.g. so it doesn't distract screen reader users), while
still exposing the message as a semantics label. Previously, the entire
RawTooltip subtree -- including its Semantics(tooltip:) node -- was skipped
whenever the tooltip was not visible, so widgets like IconButton lost their
accessibility label entirely instead of falling back to it.

Fixes flutter/flutter#189062
…edback

Replaces the custom _semanticsNodeWithLabel tree-walk helper with the
built-in tester.getSemantics(finder).tooltip, per QuncCccccc's review
suggestion. Works cleanly here because the Tooltip itself is the nearest
semantics boundary above the labelled child once wrapped in Semantics.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request ensures that Tooltip preserves its semantics label when wrapped in TooltipVisibility(visible: false) by adding a fallback Semantics widget when tooltip rendering is suppressed. Corresponding tests and a changelog entry have been added. Feedback suggests simplifying the condition in tooltip.dart by removing the redundant _tooltipMessage.isNotEmpty check, as it is already handled earlier in the build method.

ignorePointer: widget.ignorePointer ?? widget.message != null,
child: effectiveChild,
);
} else if (!excludeFromSemantics && _tooltipMessage.isNotEmpty) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check _tooltipMessage.isNotEmpty is redundant here because _tooltipMessage.isEmpty is already checked at the beginning of the build method (line 502), which results in an early return. We can safely simplify this condition to just !excludeFromSemantics.

    } else if (!excludeFromSemantics) {

@QuncCccccc QuncCccccc added the CICD Run CI/CD label Sep 17, 2026

@QuncCccccc QuncCccccc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution! Because of the volume of PRs we receive, we require that new contributors use our checklist to guide them through critical steps in creating a Flutter PR. This PR's description is missing that checklist, so it is being marked as a Draft.

Please edit the PR description to add the checklist, then ensure that you have completed all of the steps. Once you've done that, please mark the PR as ready for review.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@QuncCccccc
QuncCccccc marked this pull request as draft September 17, 2026 16:10
@hkarmoush
hkarmoush marked this pull request as ready for review September 18, 2026 12:39

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request ensures that Tooltip preserves its semantics label when wrapped in TooltipVisibility with visible set to false, adding corresponding tests and a changelog entry. Feedback on the changes suggests removing a redundant _tooltipMessage.isNotEmpty check and caching _tooltipMessage in a local variable to optimize performance by avoiding redundant text span tree traversals.

ignorePointer: widget.ignorePointer ?? widget.message != null,
child: effectiveChild,
);
} else if (!excludeFromSemantics && _tooltipMessage.isNotEmpty) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check _tooltipMessage.isNotEmpty is redundant here because there is already an early return at the beginning of the build method (line 502) if _tooltipMessage.isEmpty is true. Additionally, as a performance improvement, consider caching _tooltipMessage in a local variable at the start of the build method. Since _tooltipMessage is a getter that calls toPlainText() on widget.richMessage (which traverses the text span tree), caching it avoids redundant traversals across multiple checks and widget instantiations within the same build cycle.

    } else if (!excludeFromSemantics) {

@QuncCccccc

Copy link
Copy Markdown
Contributor

Hello, please edit the PR description to add the checklist, then ensure that you have completed all of the steps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD p: material_ui triage-design Should be looked at in design triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setting TooltipVisibility(visible: false) prevents tooltips contributing semantics

2 participants