Flutter - Using ExcludeSemantics & BlockSemantics Examples

This tutorial explains what is Semantics widget in Flutter, what it is used for, how to use it, and also how to merge semantics using MergeSemantics widget.

A Flutter application contains a bunch of widgets to build the user interface. But what are the meanings of those widgets? In Flutter, you can use Semantics to define what is the meaning of a part in the UI. Semantics is a widget that annotates the widget tree with a description of the meaning of the widgets.

Besides creating a widget tree, Flutter also creates another tree that stores the semantics of widgets. The information held by the semantics tree is important for semantic analysis tools. For Flutter Web, it can be very useful when a search engine reads a webpage. For a mobile application, it can be used by accessibility services such as Google TalkBack in Android or VoiceOver in iOS. That kind of service usually provides audible description to users and allows users to interact with an application using gestures. To do so, they need to read the screen of the used application and get the information about the user interface. Therefore, providing the correct semantics can improve the user experience for blind and visually impaired users.

Debugging Semantics

Flutter already provides a way to show the semantics of the widgets. You only need to pass showSemanticsDebugger argument to the constructor of MaterialApp and set the value to true. By doing so, Flutter will add an overlay on the widgets that allows us to see the Semantics.

  MaterialApp(
    ...
    showSemanticsDebugger: true,
  )

Some of the widgets provided by Flutter already have semantics by default, even if you do not set explicitly.

Using Semantics

To set the semantics of a subtree, you can use the constructor of Semantics. The constructor has so many available arguments and all of the arguments are optional. There are some arguments used to describe what a node is. For example, it has button argument which indicates whether the subtree represents a button. There are some arguments used to set callback actions. For example, you can create a handler for SemanticsAction.tap by passing onTap argument. The list of supported arguments along with the descriptions can be read in the Semantics - Parameters section.

Basically, to set the semantics of a widget or a subtree, what you need to do is pass it as the child argument of Semantics widget.

  Semantics({
    Key? key,
    Widget? child,
    ...
  })

Besides using the constructor above, you can also use named constructor fromProperties. It has a required argument properties whose type is SemanticsProperties. The SemanticsProperties itself is a class whose properties are used by assistive technologies. You can read the list of properties in the SemanticsProperties - Properties section.

  const Semantics.fromProperties({
    Key? key,
    Widget? child,
    bool container = false,
    bool explicitChildNodes = false,
    bool excludeSemantics = false,
    required SemanticsProperties properties,
  })

The code below shows how to set the Semantics of widgets.

  Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Semantics(
        label: 'Counter text',
        child: Text(_counter.toString(), style: const TextStyle(fontSize: 20)),
      ),
      Semantics.fromProperties(
        properties: SemanticsProperties(
          button: true,
          onTap:  () => _increaseCounter(),
        ),
        child: OutlinedButton(
          child: const Text('Increase counter'),
          onPressed: () => _increaseCounter(),
        ),
      ),
    ],
  ),

Output:

Flutter - Semantics

Using MergeSemantics

Sometimes merging the semantics of some widgets can result in a more appropriate semantics tree. For example if you have a Checkbox widget and a Text widget which is used as the label of the Checkbox. It would be better if the label from the Text and the checked value from the Checkbox is merged into a single semantic node. For that purpose, you can use a widget called MergeSemantics. MergeSemantics is a widget that merges the semantics of its descendant into one.

Be careful that merging the semantics of some widgets may result in a conflict. For example, if you merge the semantics of two Checkbox widgets, with one is in checked state and the other is in unchecked state. The resulting semantic is all the Checkbox widgets are presented as checked. Meanwhile, the labels will be merged into a string separated by newline. If the subtree has multiple nodes that can handle semanic gestures, Flutter will pick the first one in tree order to receive the callbacks.

Using MergeSemantics is very simple. You only need to pass the widget (the subtree) to be merged as child argument when calling MergeSemantics constructor.

  const MergeSemantics({ Key? key, Widget? child })

Below is an example of how to merge the semantics of two widgets.

   MergeSemantics(
    child: Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Checkbox(
          value: _isChecked,
          onChanged: (value) {
            setState(() {
              _isChecked = value;
            });
          },
        ),
        Semantics(
          label: 'Foo',
          hint: 'This is foo',
          child: const Text('Foo'),
        ),
      ],
    ),
   ),

Output:

Flutter - MergeSemantics

Semantics - Parameters

  • Key? key: The widget's key.
  • Widget? child: The widget under this widget in the tree.
  • bool container : Whether to introduce a new node in the semantics (if true), or merge the semantics with its ancestors if allowed (if false). Defaults to false
  • bool explicitChildNodes: Whether descendants of this widget are allowed to add semantic information to the SemanticsNode. Defaults to false
  • bool excludeSemantics: Whether to replace all child semantics with this node. Defaults to false
  • bool? enabled: Whether the subtree represents something that can be in enabled or disabled state.
  • bool? checked: Whether the subtree represents a checkbox or a similar widget that has a checked state, and its current state.
  • bool? selected: Whether the subtree represents something that can be in selected or unselected state, and its current state.
  • bool? toggled: Whether the subtree represents a toggle switch or a similar widget that can be in on or off state, and its current state.
  • bool? button: Whether the subtree represents a button.
  • bool? slider: Whether the subtree represents a slider.
  • bool? link: Whether the subtree represents a link.
  • bool? header: Whether the subtree represents a header.
  • bool? textField: Whether the subtree represents a text field.
  • bool? readOnly: Whether the subtree is read only.
  • bool? focusable: Whether the node is able to hold input focus.
  • bool? focused: Whether the node currently holds input focus.
  • bool? inMutuallyExclusiveGroup: Whether a semantic node is in a mutually exclusive group.
  • bool? obscured: Whether value should be obscured.
  • bool? multiline: Whether the value is coming from a field that supports multiline text editing.
  • bool? scopesRoute: Whether the node corresponds to the root of a subtree for which a route name should be announced.
  • bool? namesRoute: Whether the node contains the semantic label for a route.
  • bool? hidden: Whether the node is hidden.
  • bool? image: Whether the node represents an image.
  • bool? liveRegion: Whether the node should be considered a live region.
  • int? maxValueLength: The maximum number of characters that can be entered into an editable text field.
  • int? currentValueLength: The current number of characters entered into an editable text.
  • String? label: Description of the widget.
  • String? value: Description of the value of the widget.
  • String? increasedValue: The new value for value property after a SemanticsAction.increase action has been performed.
  • String? decreasedValue: The new value for value property after a SemanticsAction.decrease action has been performed.
  • String? hint: Description of the result of an action performed on the widget.
  • String? onTapHint: The hint text for a tap action.
  • String? onLongPressHint: The hint text for a long press action.
  • TextDirection? textDirection: The reading direction of the label, value, hint, decreasedValue, and increasedValue.
  • SemanticsSortKey? sortKey: The position of this node among its siblings in the traversal sort order.
  • SemanticsTag? tagForChildren: A tag to be applied to the child SemanticsNodes.
  • VoidCallback? onTap: The handler for SemanticsAction.tap.
  • VoidCallback? onLongPress: The handler for SemanticsAction.longPress.
  • VoidCallback? onScrollLeft: The handler for SemanticsAction.scrollLeft.
  • VoidCallback? onScrollRight: The handler for SemanticsAction.scrollRight.
  • VoidCallback? onScrollUp: The handler for SemanticsAction.scrollUp.
  • VoidCallback? onScrollDown: The handler for SemanticsAction.scrollDown.
  • VoidCallback? onIncrease: The handler for SemanticsAction.increase.
  • VoidCallback? onDecrease: The handler for SemanticsAction.decrease.
  • VoidCallback? onCopy: The handler for SemanticsAction.copy.
  • VoidCallback? onCut: The handler for SemanticsAction.cut.
  • VoidCallback? onPaste: The handler for SemanticsAction.paste.
  • VoidCallback? onDismiss: The handler for SemanticsAction.dismiss.
  • MoveCursorHandler? onMoveCursorForwardByCharacter: The handler for SemanticsAction.moveCursorForwardByCharacter.
  • MoveCursorHandler? onMoveCursorBackwardByCharacter: The handler for SemanticsAction.moveCursorBackwardByCharacter.
  • SetSelectionHandler? onSetSelection: The handler for SemanticsAction.setSelection.
  • VoidCallback? onDidGainAccessibilityFocus: The handler for SemanticsAction.didGainAccessibilityFocus.
  • VoidCallback? onDidLoseAccessibilityFocus: The handler for SemanticsAction.didLoseAccessibilityFocus.
  • Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions: A map from each supported CustomSemanticsAction to a provided handler.

?: Value can be null

Semantics.fromProperties - Parameters

  • Key? key: The widget's key.
  • Widget? child: The widget under this widget in tree.
  • bool container : Whether to introduce a new node in the semantics (if true), or merge the semantics with its ancestors if allowed (if false). Defaults to false
  • bool explicitChildNodes: Whether descendants of this widget are allowed to add semantic information to the SemanticsNode. Defaults to false
  • bool excludeSemantics: Whether to replace all child semantics with this node. Defaults to false
  • @required SemanticsProperties properties: Properties used by assistive technologies to increase accessibility of the application.

?: Value can be null

SemanticsProperties - Properties

  • bool? enabled: Whether the subtree represents something that can be in enabled or disabled state.
  • bool? checked: Whether the subtree represents a checkbox or a similar widget that has a checked state, and its current state.
  • bool? selected: Whether the subtree represents something that can be in selected or unselected state, and its current state.
  • bool? toggled: Whether the subtree represents a toggle switch or a similar widget that can be in on or off state, and its current state.
  • bool? button: Whether the subtree represents a button.
  • bool? link: Whether the subtree represents a link.
  • bool? header: Whether the subtree represents a header.
  • bool? textField: Whether the subtree represents a text field.
  • bool? slider: Whether the subtree represents a slider.
  • bool? readOnly: Whether the subtree is read only.
  • bool? focusable: Whether the node is able to hold input focus..
  • bool? focused: Whether the node currently holds input focus..
  • bool? inMutuallyExclusiveGroup: Whether a semantic node is in a mutually exclusive group.
  • bool? hidden: Whether the node is hidden.
  • bool? obscured: Whether value should be obscured..
  • bool? multiline: Whether the value is coming from a field that supports multiline text editing.
  • bool? scopesRoute: Whether the node corresponds to the root of a subtree for which a route name should be announced.
  • bool? namesRoute: Whether the node contains the semantic label for a route.
  • bool? image: Whether the node represents an image.
  • bool? liveRegion: Whether the node should be considered a live region.
  • int? maxValueLength: The maximum number of characters that can be entered into an editable text field.
  • int? currentValueLength: The current number of characters entered into an editable text.
  • String? label: Description of the widget.
  • String? value: Description of the value of the widget.
  • String? increasedValue: The new value for value property after a SemanticsAction.increase action has been performed.
  • String? decreasedValue: The new value for value property after a SemanticsAction.decrease action has been performed.
  • String? hint: Description of the result of an action performed on the widget.
  • SemanticsHintOverrides? hintOverrides: Hint values which override the default hints on supported platforms..
  • TextDirection? textDirection: The reading direction of the label, value, hint, decreasedValue, and increasedValue.
  • SemanticsSortKey? sortKey: The position of this node among its siblings in the traversal sort order.
  • SemanticsTag? tagForChildren: A tag to be applied to the child SemanticsNodes.
  • VoidCallback? onTap: The handler for SemanticsAction.tap.
  • VoidCallback? onLongPress: The handler for SemanticsAction.longPress.
  • VoidCallback? onScrollLeft: The handler for SemanticsAction.scrollLeft.
  • VoidCallback? onScrollRight: The handler for SemanticsAction.scrollRight.
  • VoidCallback? onScrollUp: The handler for SemanticsAction.scrollUp.
  • VoidCallback? onScrollDown: The handler for SemanticsAction.scrollDown.
  • VoidCallback? onIncrease: The handler for SemanticsAction.increase.
  • VoidCallback? onDecrease: The handler for SemanticsAction.decrease.
  • VoidCallback? onCopy: The handler for SemanticsAction.copy.
  • VoidCallback? onCut: The handler for SemanticsAction.cut.
  • VoidCallback? onPaste: The handler for SemanticsAction.paste.
  • MoveCursorHandler? onMoveCursorForwardByCharacter: The handler for SemanticsAction.moveCursorForwardByCharacter.
  • MoveCursorHandler? onMoveCursorBackwardByCharacter: The handler for SemanticsAction.moveCursorBackwardByCharacter.
  • MoveCursorHandler? onMoveCursorForwardByWord: The handler for SemanticsAction.moveCursorForwardByWord.
  • MoveCursorHandler? onMoveCursorBackwardByWord: The handler for SemanticsAction.moveCursorBackwardByWord.
  • SetSelectionHandler? onSetSelection: The handler for SemanticsAction.setSelection.
  • VoidCallback? onDidGainAccessibilityFocus: The handler for SemanticsAction.didGainAccessibilityFocus.
  • VoidCallback? onDidLoseAccessibilityFocus: The handler for SemanticsAction.didLoseAccessibilityFocus.
  • VoidCallback? onDismiss: The handler for SemanticsAction.dismiss.
  • Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions: A map from each supported CustomSemanticsAction to a provided handler.

?: Value can be null

Full Code

  import 'package:flutter/material.dart';
  import 'package:flutter/rendering.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter Tutorial',
        showSemanticsDebugger: true,
        home:  Scaffold(
          appBar: AppBar(
            title: const Text('Woolha.com Flutter Tutorial'),
            backgroundColor: Colors.teal,
          ),
          body: BlockSemantics(),
        ),
      );
    }
  }
  
  class SemanticsExample extends StatefulWidget {
  
    @override
    State<StatefulWidget> createState() {
      return _SemanticsExampleState ();
    }
  }
  
  class _SemanticsExampleState extends State<SemanticsExample> {
  
    bool _isChecked = false;
    int _counter = 0;
  
    void _increaseCounter() {
      setState(() {
        _counter += 1;
      });
    }
  
    @override
    Widget build(BuildContext context) {
      return SizedBox(
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Semantics(
              label: 'Counter text',
              child: Text(_counter.toString(), style: const TextStyle(fontSize: 20)),
            ),
            Semantics.fromProperties(
              properties: SemanticsProperties(
                button: true,
                onTap:  () => _increaseCounter(),
              ),
              child: OutlinedButton(
                child: const Text('Increase counter'),
                onPressed: () => _increaseCounter(),
              ),
            ),
            MergeSemantics(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Checkbox(
                    value: _isChecked,
                    onChanged: (value) {
                      setState(() {
                        _isChecked = value;
                      });
                    },
                  ),
                  Semantics(
                    label: 'Foo',
                    hint: 'This is foo',
                    child: const Text('Foo'),
                  ),
                ],
              ),
             ),
          ],
        ),
      );
    }
  }

Output:

Flutter - Semantics - Full Code

Summary

Semantics widget can be used to set the meanings of subtrees in a Flutter application. That's become more important if the application needs to give a good experience for blind and visually impaired users who usually rely on accessibility services. By providing appropriate semantics, it helps the accessibility services to understand what is being displayed on the screen and what kinds of actions can be done. If you need to merge the semantics of a subtree, you can use the MergeSemantics widget.

You can also read about: