Skip to content
Blog

MergeSemantics Widget in Flutter: Grouping Semantic Information

Learn how Flutter's MergeSemantics widget groups related semantic nodes for better screen reader announcements and improved accessibility.

Published on September 19, 2026

AI Assistant

MergeSemantics Widget in Flutter: Grouping Semantic Information

The MergeSemantics widget is a powerful tool for controlling how Flutter’s accessibility system groups related content. It forces all descendant semantic nodes to be merged into a single node, ensuring screen readers announce related information as a cohesive unit.

Why MergeSemantics Matters

Without MergeSemantics, Flutter automatically merges adjacent text nodes. But sometimes you need explicit control over how related content is grouped. This widget ensures that multi-part information is read together, not as separate fragments.

Basic Usage

MergeSemantics(
  child: Row(
    children: [
      const Icon(Icons.calendar_today),
      const SizedBox(width: 8),
      Text('September 19, 2026'),
    ],
  ),
)
// Screen reader: "calendar_today September 19, 2026"
// Without MergeSemantics: "calendar_today" then "September 19, 2026"

Common Use Cases

Product Cards

MergeSemantics(
  child: Card(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Image.asset('product.jpg'),
        Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text('Wireless Headphones'),
              const SizedBox(height: 4),
              const Text('Sony WH-1000XM5'),
              const SizedBox(height: 8),
              Row(
                children: [
                  const Icon(Icons.star, color: Colors.amber),
                  const Text('4.8'),
                  const SizedBox(width: 8),
                  const Text('(2,345 reviews)'),
                ],
              ),
              const SizedBox(height: 8),
              const Text('\$349.99'),
            ],
          ),
        ),
      ],
    ),
  ),
)
// Screen reader: "Wireless Headphones Sony WH-1000XM5 star 4.8 (2,345 reviews) $349.99"

Status Messages

MergeSemantics(
  child: Row(
    children: [
      const Icon(Icons.check_circle, color: Colors.green),
      const SizedBox(width: 8),
      const Text('Order confirmed'),
      const SizedBox(width: 16),
      const Text('September 19, 2026'),
      const SizedBox(width: 8),
      const Text('at 2:30 PM'),
    ],
  ),
)
// Screen reader: "check_circle Order confirmed September 19, 2026 at 2:30 PM"

Form Fields with Validation

MergeSemantics(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text('Email Address'),
      const SizedBox(height: 8),
      TextFormField(
        decoration: const InputDecoration(
          hintText: 'you@example.com',
        ),
        validator: (value) {
          if (value == null || value.isEmpty) {
            return 'Please enter your email';
          }
          return null;
        },
      ),
    ],
  ),
)
// Screen reader: "Email Address you@example.com"
// Or on error: "Email Address Please enter your email"

MergeSemantics vs ExcludeSemantics

WidgetPurposeEffect
MergeSemanticsGroups related nodesCombines children into one semantic node
ExcludeSemanticsHides contentRemoves children from semantics entirely
// MergeSemantics: Groups these into one announcement
MergeSemantics(
  child: Row(
    children: [
      const Text('Subtotal:'),
      const Text('\$100'),
      const Text(' + Tax:'),
      const Text('\$8'),
      const Text(' = Total:'),
      const Text('\$108'),
    ],
  ),
)
// Screen reader: "Subtotal: $100 + Tax: $8 = Total: $108"

// ExcludeSemantics: Hides decorative elements
ExcludeSemantics(
  child: Image.asset('decorative_border.png'),
)
// Screen reader: ignores this element entirely

Controlling Merge Depth

By default, MergeSemantics merges all descendants. For more control, combine it with Semantics widgets:

MergeSemantics(
  child: Column(
    children: [
      // This node is part of the merge
      Semantics(
        header: true,
        child: const Text('Section Title'),
      ),
      // These nodes are also merged
      Semantics(
        label: 'Description text',
        child: const Text('This is a detailed description.'),
      ),
    ],
  ),
)
// All descendants are merged into one semantic node

Common Patterns

Price Breakdown

MergeSemantics(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text('Subtotal'),
            Text('\$${subtotal.toStringAsFixed(2)}'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text('Shipping'),
            Text('\$${shipping.toStringAsFixed(2)}'),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text('Tax'),
            Text('\$${tax.toStringAsFixed(2)}'),
          ],
        ),
        const Divider(),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            const Text('Total', style: TextStyle(fontWeight: FontWeight.bold)),
            Text('\$${total.toStringAsFixed(2)}',
                style: const TextStyle(fontWeight: FontWeight.bold)),
          ],
        ),
      ],
    ),
  ),
)
// Screen reader: "Subtotal $100.00 Shipping $9.99 Tax $8.00 Total $117.99"

Chat Message

MergeSemantics(
  child: ListTile(
    leading: CircleAvatar(child: Text('JD')),
    title: const Text('John Doe'),
    subtitle: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('Hey, are you free tomorrow?'),
        const SizedBox(height: 4),
        Text(
          '2:30 PM',
          style: TextStyle(color: Colors.grey[600], fontSize: 12),
        ),
      ],
    ),
  ),
)
// Screen reader: "JD John Doe Hey, are you free tomorrow? 2:30 PM"

Best Practices

  1. Use MergeSemantics for multi-part information — dates, prices, ratings
  2. Don’t over-merge — keep semantic meaning clear
  3. Test with screen readers — verify the merged announcement makes sense
  4. Combine with ExcludeSemantics — hide decorative elements within merged groups
  5. Consider the reading order — merged content is read left-to-right, top-to-bottom

MergeSemantics is an essential tool for fine-tuning your app’s accessibility. By thoughtfully grouping related content, you ensure that screen reader users get a clear, coherent understanding of your UI.