flutter-accessibility

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

flutter-accessibility-and-adaptive-design

Flutter 无障碍与自适应设计

Goal

目标

Implements, audits, and enforces accessibility (a11y) and adaptive design standards in Flutter applications. Ensures compliance with WCAG 2 and EN 301 549 by applying proper semantic roles, contrast ratios, tap target sizes, and assistive technology integrations. Constructs adaptive layouts that respond to available screen space and input modalities (touch, mouse, keyboard) without relying on hardware-specific checks or locked orientations.
在Flutter应用中实现、审核并强制执行无障碍(a11y)和自适应设计标准。通过应用正确的语义角色、对比度、点击目标尺寸以及辅助技术集成,确保符合WCAG 2和EN 301 549标准。构建可响应可用屏幕空间和输入模式(触控、鼠标、键盘)的自适应布局,无需依赖特定硬件检测或固定屏幕方向。

Decision Logic

决策逻辑

When implementing UI components, follow this decision tree to determine the required accessibility and adaptive design implementations:
  1. Is the app targeting Flutter Web?
    • Yes: Ensure
      SemanticsBinding.instance.ensureSemantics();
      is called at startup. Explicitly map custom widgets to
      SemanticsRole
      to generate correct ARIA tags.
    • No: Proceed to standard mobile/desktop semantics.
  2. Is the widget interactive?
    • Yes: Wrap in
      Semantics
      with
      button: true
      or appropriate role. Ensure tap target is $\ge$ 48x48 logical pixels.
    • No: Ensure text contrast meets WCAG standards (4.5:1 for small text, 3.0:1 for large text).
  3. Does the layout need to change based on screen size?
    • Yes: Use
      LayoutBuilder
      or
      MediaQuery.sizeOf(context)
      . Do NOT use
      MediaQuery.orientation
      or hardware type checks (e.g.,
      isTablet
      ).
    • No: Use standard flexible widgets (
      Expanded
      ,
      Flexible
      ) to fill available space.
  4. Does the app support desktop/web input?
    • Yes: Implement
      FocusableActionDetector
      ,
      Shortcuts
      , and
      MouseRegion
      for hover states and keyboard traversal.
    • No: Focus primarily on touch targets and screen reader traversal order.
实现UI组件时,请按照以下决策树确定所需的无障碍和自适应设计实现方案:
  1. 应用是否适配Flutter Web?
    • 是: 确保启动时调用
      SemanticsBinding.instance.ensureSemantics();
      。显式将自定义组件映射到
      SemanticsRole
      以生成正确的ARIA标签。
    • 否: 继续使用标准移动端/桌面端语义规则。
  2. 组件是否可交互?
    • 是: 用设置了
      button: true
      或对应角色的
      Semantics
      包裹组件。确保点击目标尺寸≥48x48逻辑像素。
    • 否: 确保文本对比度符合WCAG标准(小文本4.5:1,大文本3.0:1)。
  3. 布局是否需要根据屏幕尺寸调整?
    • 是: 使用
      LayoutBuilder
      MediaQuery.sizeOf(context)
      。禁止使用
      MediaQuery.orientation
      或硬件类型检测(例如
      isTablet
      )。
    • 否: 使用标准弹性组件(
      Expanded
      Flexible
      )填充可用空间。
  4. 应用是否支持桌面端/网页端输入?
    • 是: 实现
      FocusableActionDetector
      Shortcuts
      MouseRegion
      来支持悬停状态和键盘遍历。
    • 否: 主要关注触控目标和屏幕阅读器遍历顺序。

Instructions

使用说明

  1. Initialize Web Accessibility (If Applicable) For web targets, accessibility is disabled by default for performance. Force enable it at the entry point.
    dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/semantics.dart';
    
    void main() {
      runApp(const MyApp());
      if (kIsWeb) {
        SemanticsBinding.instance.ensureSemantics();
      }
    }
  2. Apply Semantic Annotations Use
    Semantics
    ,
    MergeSemantics
    , and
    ExcludeSemantics
    to build a clean accessibility tree. For custom web components, explicitly define the
    SemanticsRole
    .
    dart
    Semantics(
      role: SemanticsRole.button,
      label: 'Submit Form',
      hint: 'Press to send your application',
      button: true,
      child: GestureDetector(
        onTap: _submit,
        child: const CustomButtonUI(),
      ),
    )
  3. Enforce Tap Target and Contrast Standards Ensure all interactive elements meet the 48x48 dp minimum (Android) or 44x44 pt minimum (iOS/Web).
    dart
    // Example of enforcing minimum tap target size
    ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: 48.0,
        minHeight: 48.0,
      ),
      child: IconButton(
        icon: const Icon(Icons.info),
        onPressed: () {},
        tooltip: 'Information', // Tooltip.message follows Tooltip.child in semantics tree
      ),
    )
  4. Implement Adaptive Layouts Use
    LayoutBuilder
    to respond to available space rather than device type.
    dart
    LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return const WideDesktopLayout();
        } else {
          return const NarrowMobileLayout();
        }
      },
    )
  5. Implement Keyboard and Mouse Support Use
    FocusableActionDetector
    for custom controls to handle focus, hover, and keyboard shortcuts simultaneously.
    dart
    FocusableActionDetector(
      onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus),
      onShowHoverHighlight: (hasHover) => setState(() => _hasHover = hasHover),
      actions: <Type, Action<Intent>>{
        ActivateIntent: CallbackAction<Intent>(
          onInvoke: (intent) {
            _performAction();
            return null;
          },
        ),
      },
      child: MouseRegion(
        cursor: SystemMouseCursors.click,
        child: CustomWidget(isHovered: _hasHover, isFocused: _hasFocus),
      ),
    )
  6. Manage Focus Traversal Group related widgets using
    FocusTraversalGroup
    to ensure logical tab order for keyboard users.
    dart
    FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Column(
        children: [
          FocusTraversalOrder(
            order: const NumericFocusOrder(1.0),
            child: TextField(),
          ),
          FocusTraversalOrder(
            order: const NumericFocusOrder(2.0),
            child: ElevatedButton(onPressed: () {}, child: Text('Submit')),
          ),
        ],
      ),
    )
  7. Validate Accessibility via Automated Tests STOP AND ASK THE USER: "Would you like me to generate widget tests to validate accessibility guidelines (contrast, tap targets) for your UI?" If yes, implement the
    AccessibilityGuideline
    API in the test suite:
    dart
    import 'package:flutter_test/flutter_test.dart';
    
    void main() {
      testWidgets('Validates a11y guidelines', (WidgetTester tester) async {
        final SemanticsHandle handle = tester.ensureSemantics();
        await tester.pumpWidget(const MyApp());
    
        await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
        await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
        await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
        await expectLater(tester, meetsGuideline(textContrastGuideline));
        
        handle.dispose();
      });
    }
  1. 初始化网页端无障碍能力(如适用) 针对网页端目标,出于性能考虑无障碍能力默认是关闭的,需要在入口文件强制开启。
    dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/semantics.dart';
    
    void main() {
      runApp(const MyApp());
      if (kIsWeb) {
        SemanticsBinding.instance.ensureSemantics();
      }
    }
  2. 添加语义注解 使用
    Semantics
    MergeSemantics
    ExcludeSemantics
    构建清晰的无障碍树。针对自定义网页组件,需要显式定义
    SemanticsRole
    dart
    Semantics(
      role: SemanticsRole.button,
      label: 'Submit Form',
      hint: 'Press to send your application',
      button: true,
      child: GestureDetector(
        onTap: _submit,
        child: const CustomButtonUI(),
      ),
    )
  3. 强制执行点击目标和对比度标准 确保所有可交互元素符合最低尺寸要求:安卓端48x48 dp,iOS/网页端44x44 pt。
    dart
    // 强制执行最小点击目标尺寸示例
    ConstrainedBox(
      constraints: const BoxConstraints(
        minWidth: 48.0,
        minHeight: 48.0,
      ),
      child: IconButton(
        icon: const Icon(Icons.info),
        onPressed: () {},
        tooltip: 'Information', // Tooltip.message在语义树中跟在Tooltip.child后面
      ),
    )
  4. 实现自适应布局 使用
    LayoutBuilder
    响应可用空间,而非设备类型判断。
    dart
    LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return const WideDesktopLayout();
        } else {
          return const NarrowMobileLayout();
        }
      },
    )
  5. 实现键盘和鼠标支持 自定义控件使用
    FocusableActionDetector
    同时处理聚焦、悬停和键盘快捷键逻辑。
    dart
    FocusableActionDetector(
      onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus),
      onShowHoverHighlight: (hasHover) => setState(() => _hasHover = hasHover),
      actions: <Type, Action<Intent>>{
        ActivateIntent: CallbackAction<Intent>(
          onInvoke: (intent) {
            _performAction();
            return null;
          },
        ),
      },
      child: MouseRegion(
        cursor: SystemMouseCursors.click,
        child: CustomWidget(isHovered: _hasHover, isFocused: _hasFocus),
      ),
    )
  6. 管理焦点遍历顺序 使用
    FocusTraversalGroup
    对相关组件分组,确保键盘用户的Tab遍历顺序符合逻辑。
    dart
    FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Column(
        children: [
          FocusTraversalOrder(
            order: const NumericFocusOrder(1.0),
            child: TextField(),
          ),
          FocusTraversalOrder(
            order: const NumericFocusOrder(2.0),
            child: ElevatedButton(onPressed: () {}, child: Text('Submit')),
          ),
        ],
      ),
    )
  7. 通过自动化测试验证无障碍能力 请停下来询问用户: "你是否需要我生成组件测试来验证你的UI符合无障碍指南(对比度、点击目标)?" 如果需要,在测试套件中实现
    AccessibilityGuideline
    API:
    dart
    import 'package:flutter_test/flutter_test.dart';
    
    void main() {
      testWidgets('Validates a11y guidelines', (WidgetTester tester) async {
        final SemanticsHandle handle = tester.ensureSemantics();
        await tester.pumpWidget(const MyApp());
    
        await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
        await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
        await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
        await expectLater(tester, meetsGuideline(textContrastGuideline));
        
        handle.dispose();
      });
    }

Constraints

约束条件

  • Never lock device orientation. Apps must support both portrait and landscape modes to comply with accessibility standards.
  • Never use hardware type checks (e.g., checking if the device is a phone or tablet) for layout decisions. Always use
    MediaQuery.sizeOf
    or
    LayoutBuilder
    .
  • Never use
    MediaQuery.orientation
    near the top of the widget tree to switch layouts. Rely on available width/height breakpoints.
  • Always provide
    Semantics
    labels
    for custom interactive widgets or images that convey meaning.
  • Always use
    PageStorageKey
    on scrollable lists that do not change layout during orientation shifts to preserve scroll state.
  • Do not consume infinite horizontal space for text fields or lists on large screens; constrain maximum widths for readability.
  • Account for Tooltip semantics order:
    Tooltip.message
    is visited immediately after
    Tooltip.child
    in the semantics tree. Update tests accordingly.
  • 禁止锁定设备方向。 应用必须同时支持竖屏和横屏模式以符合无障碍标准。
  • 禁止使用硬件类型检测(例如判断设备是手机还是平板)来做布局决策,始终使用
    MediaQuery.sizeOf
    LayoutBuilder
  • 禁止在组件树顶层使用
    MediaQuery.orientation
    切换布局,依赖可用宽高断点实现适配。
  • 必须为有表意作用的自定义可交互组件或图片提供
    Semantics
    标签。
  • 方向切换时布局不变的可滚动列表必须使用
    PageStorageKey
    来保存滚动位置。
  • 大屏幕下文本框或列表禁止无限占满横向空间;需要限制最大宽度保证可读性。
  • 注意Tooltip语义顺序: 语义树中
    Tooltip.message
    会紧跟在
    Tooltip.child
    后面被访问,测试时需要对应调整。