Flutter 3.41 is live! Check out theFlutter 3.41 blog post!
Semantics Order of the Overlay Entries in Modal Routes
The scope of the modal route has a higher semantics traverse order than its modal barrier.
These breaking change docs are accurate, as of the release under which they are published. Over time, the workarounds described here might become inaccurate. We don't, in general, keep these breaking change docs up to date as of each release.
Thebreaking change index file lists the docs created for each release.
Summary
#We changed the semantics traverse order of the overlay entries in modal routes. Accessibility talk back or voice over now focuses the scope of a modal route first instead of its modal barrier.
Context
# The modal route has two overlay entries, the scope and the modal barrier. The scope is the actual content of the modal route, and the modal barrier is the background of the route if its scope does not cover the entire screen. If the modal route returns true forbarrierDismissible, the modal barrier becomes accessibility focusable because users can tap the modal barrier to pop the modal route. This change specifically made the accessibility to focus the scope first before the modal barrier.
Description of change
#We added additional semantics node above both the overlay entries of modal routes. Those semantics nodes denote the semantics traverse order of these two overlay entries. This also changed the structure of semantics tree.
Migration guide
#If your tests start failing due to semantics tree changes after the update, you can migrate your code by expecting a new node on above of the modal route overlay entries.
Code before migration:
import'dart:ui';import'package:flutter_test/flutter_test.dart';import'package:flutter/rendering.dart';import'package:flutter/material.dart';voidmain(){testWidgets('example test',(WidgetTestertester)async{finalSemanticsHandlehandle=tester.binding.pipelineOwner.ensureSemantics();// Build our app and trigger a frame.awaittester.pumpWidget(MaterialApp(home:Scaffold(body:Text('test'))));finalSemanticsNoderoot=tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;finalSemanticsNodefirstNode=getChild(root);expect(firstNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));// Fixes the test by expecting an additional node above the scope route.finalSemanticsNodesecondNode=getChild(firstNode);expect(secondNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));finalSemanticsNodethirdNode=getChild(secondNode);expect(thirdNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute),true);finalSemanticsNodeforthNode=getChild(thirdNode);expect(forthNode.rect,Rect.fromLTRB(0.0,0.0,56.0,14.0));expect(forthNode.label,'test');handle.dispose();});}SemanticsNodegetChild(SemanticsNodenode){SemanticsNodechild;boolvisiter(SemanticsNodetarget){child=target;returnfalse;}node.visitChildren(visiter);returnchild;}Code after migration:
import'dart:ui';import'package:flutter_test/flutter_test.dart';import'package:flutter/rendering.dart';import'package:flutter/material.dart';voidmain(){testWidgets('example test',(WidgetTestertester)async{finalSemanticsHandlehandle=tester.binding.pipelineOwner.ensureSemantics();// Build our app and trigger a frame.awaittester.pumpWidget(MaterialApp(home:Scaffold(body:Text('test'))));finalSemanticsNoderoot=tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode;finalSemanticsNodefirstNode=getChild(root);expect(firstNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));// Fixes the test by expecting an additional node above the scope route.finalSemanticsNodesecondNode=getChild(firstNode);expect(secondNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));finalSemanticsNodethirdNode=getChild(secondNode);expect(thirdNode.rect,Rect.fromLTRB(0.0,0.0,800.0,600.0));expect(thirdNode.hasFlag(SemanticsFlag.scopesRoute),true);finalSemanticsNodeforthNode=getChild(thirdNode);expect(forthNode.rect,Rect.fromLTRB(0.0,0.0,56.0,14.0));expect(forthNode.label,'test');handle.dispose();});}SemanticsNodegetChild(SemanticsNodenode){SemanticsNodechild;boolvisiter(SemanticsNodetarget){child=target;returnfalse;}node.visitChildren(visiter);returnchild;}Timeline
# Landed in version: 1.19.0
In stable release: 1.20
References
#API documentation:
Relevant issue:
Relevant PR:
Unless stated otherwise, the documentation on this site reflects Flutter 3.38.6. Page last updated on 2025-10-28.View source orreport an issue.