Description: 1.In the attached sample, when TalkBack is enabled, the tooltip content is read automatically, even before the user taps on the tooltip.
2.This issue can be resolved by adding MergeSemantics to the tooltip's child. Example:
Tooltip(
triggerMode: TooltipTriggerMode.tap,
richMessage: TextSpan(children: [
TextSpan(text: 'title Hello'),
TextSpan(text: 'This is tool tip content ABCEDFE'),
]),
child: MergeSemantics(child: child),
)
3.However, applying this fix leads to a new issue: When using Row, the reading order is broken. For example, if you uncomment the line:
Text('12345678'), // Issue 2
TalkBack will read "12345678" before "Card Number. Tap for Info.", which is not the intended order.
This issue impacts screen reader users by either exposing tooltip content too early or disrupting reading order, affecting accessibility compliance.
Expected Behavior:
Tooltip content (richMessage) should not be read automatically.
Screen reader should read in visual order, i.e., "Card Number. Tap for info." → "12345678".
Actual Behavior:
Without MergeSemantics:
When TalkBack is enabled, the tooltip content (richMessage) is read out immediately, even though the user has not tapped the tooltip.
This breaks expected behavior where tooltip content should only be read on interaction.
With MergeSemantics added to the tooltip child:
Tooltip content behaves correctly (only read on tap). ✅
However, in a Row layout, the reading order is incorrect.
TalkBack reads the trailing widget (e.g., Text('12345678')) first,
Then it reads the tooltip label ("Card Number. Tap for info.") afterwards — which is out of visual/logical order.
Code Sample:
[import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
body: ExampleFixedWithRichText(),
),
));
}
class ExampleFixedWithRichText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Semantics(
container: true,
child:
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
CustomAccessibleTooltip(
child: RichText(
text: TextSpan(
style: TextStyle(color: Colors.black, fontSize: 16),
children: [
TextSpan(text: 'Card Number'),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Padding(
padding: const EdgeInsets.only(left: 4.0),
child: Icon(
Icons.info_outline,
size: 16,
semanticLabel: 'Tap for info',
),
),
),
],
),
),
),
//Issue 2
// Text('12345678'),
]),
),
),
);
}
}
class CustomAccessibleTooltip extends StatelessWidget {
final Widget child;
const CustomAccessibleTooltip({
required this.child,
});
@override
Widget build(BuildContext context) {
return Tooltip(
triggerMode: TooltipTriggerMode.tap,
richMessage: TextSpan(children: [
TextSpan(text: 'title Hello'),
TextSpan(text: 'This is tool tip content ABCEDFE'),
]),
// Comment/uncomment the line below to toggle the bug behavior
// child: MergeSemantics(child: child),
child: child,
);
}
}
]
Flutter doctor output
[✓] Flutter (Channel stable, 3.27.3, on macOS 15.4 24E248 darwin-x64, locale
en-GB)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.2)
[✓] VS Code (version 1.99.0)
[✓] Connected device (2 available)
[✓] Network resources