Skip to content

Inconsistent A11Y focus for non-dismissible bottom sheet  #115808

Closed
@jiahaog

Description

@jiahaog

When showModalBottomSheet is called with isDismissible false, the default A11Y focus on bottom sheet is not always on the first element.

Steps to Reproduce

With talkback

Talkback version: 12.2
Device: pixel6
OS: Android 12

The following code sample adds two ways to open the bottom sheet - from the floating action button, and from the app bar.

With the following code sample, open the bottom sheet by pressing the floating action button.

When isDismissible is false, the default A11Y focus is inconsistent, depending on where the bottom sheet was triggered from.

  • Triggered from the app bar button - the default A11Y will focus on the button (bad - expected focus to be on the title)
  • Triggered from the floating action button - the default A11Y will focus on the title (good)

When isDismissible is true, the default A11Y focus will be on the title (good).

Code sample
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _showBottomSheet() {
    showModalBottomSheet<SafeArea>(
      context: context,
      // !!! This is very important to reproduce the a11y focus issue. !!!
      // Without it, the title will always get focused by default.
      // With it, trigger from app bar with focus on button while trigger from
      // floating action button will focus on title.
      isDismissible: false,
      builder: (_) => Semantics(
        scopesRoute: true,
        explicitChildNodes: true,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('This is title'),
            TextButton(
              onPressed: Navigator.of(context).pop,
              child: const Text('Ok'),
            ),
          ],
        ),
      ),
    );
  }

  void _incrementCounter() {
    _showBottomSheet();
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title, style: TextStyle(fontFamily: 'ProductSans')),
        actions: [
          TextButton(
            onPressed: _showBottomSheet,
            child: Text('ShowBottomSheet'),
          )
        ],
      ),
      body: Center(
        child: Text(
          'Button tapped $_counter time${_counter == 1 ? '' : 's'}.',
          key: Key('CountText'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
      ),
    );
  }
}

See b/237621905 for more details.

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)customer: money (g3)f: material designflutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.team-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions