flutter-improving-accessibility について
Flutter アプリのアクセシビリティを点検・改善する開発スキルです。Material と Cupertino の標準セマンティクスを活用し、スクリーンリーダー、文字サイズ、コントラスト、テストを扱います。
主な機能
- 01既存のアクセシビリティ規約を再利用
- 02カスタムコントロールにセマンティクスを追加
- 03文字サイズとコントラストへの対応
- 04タッチ領域と読み上げ順の確認
- 05アクセシビリティガイドラインのテスト
活用シーン
- 01Flutter 画面のアクセシビリティ修正
- 02独自描画やアイコンのみの操作部品にラベルを付与
- 03大きな文字サイズでのレイアウト確認
- 04セマンティクスとタッチ領域のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thesimpleapp/ flutter-app-skills --skill flutter-improving-accessibilityインストール後、Claude Code で/flutter-improving-accessibilityを入力して読み込まれたか確認します。
同梱ファイルreferences/recipes.md
詳細
Flutter のアクセシビリティ
組み込みの Material および Cupertino ウィジェットを優先してください。これらはすでに TalkBack/VoiceOver/JAWS/NVDA のツリーを出力します。カスタム描画またはジェスチャー対応のウィジェットに限り、Semantics(または Semantics.fromProperties)を追加してください。
このリポジトリに Semantics ヘルパー、a11y テストハーネス、ラベル付きコントロールの規約、または semanticLabel を持つ押下可能な要素がすでにある場合は、それを拡張してください。表示テキストを繰り返す Semantics(label:) で二重にラップしないでください。
公式ドキュメント:アクセシビリティ、支援技術、UI のデザインとスタイリング、アクセシビリティのテスト。
参考資料
カスタムコントロール、merge/exclude/block、MediaQuery の接続、またはガイドラインテストを実装する場合は、references/recipes.md を読んでください。
まず検出する
リポジトリ内で Semantics(、MergeSemantics、ExcludeSemantics、BlockSemantics、semanticLabel、excludeFromSemantics、tooltip:、meetsGuideline(、ensureSemantics(、highContrastTheme、accessibleNavigationOf、textScalerOf を検索してください。近くにある本番画面を開き、そのパターンを踏襲してください。
厳守事項
セマンティクス
- カスタム描画/
GestureDetector/InkWell:ノードがコントロールとして読み上げられるよう、label、ロールフラグ(button、slider、textField)、tooltip/hint、onTapを設定します。 - ロールはフラグです:
Semantics(button: true, label: 'Submit')。「button」をラベルに含めないでください。label/hint/tooltipには、周囲にDirectionalityがあるか、明示的なtextDirectionが必要です。一時的なステータスにはliveRegion: trueを設定します。 container: trueはSemanticsNodeを導入します。TalkBack/VoiceOverで独立したフォーカス位置にする場合に使用します。
結合、除外、ブロック
- 1つのコントロールと、その表示テキスト:両方を
MergeSemanticsで囲みます。チェックボックスを2つ、タップ可能な子ウィジェットを2つ、または競合するselected/checked値を結合しないでください。 ExcludeSemanticsは、装飾的または重複した子孫にのみ使用します。Semantics(excludeSemantics: true)は、子のセマンティクスをすべてこのノードで置き換えます。モーダルやDrawerの背後に描画されたウィジェットを隠す場合は、オーバーレイにBlockSemanticsを設定し、オーバーレイの子にExcludeSemanticsを設定しないでください。
タップ領域と名前
- タップ可能なターゲットはすべて48×48論理ピクセル以上(
kMinInteractiveDimension)にします。Cupertinoの最小値はkMinInteractiveDimensionCupertino(44.0)です。iconSizeに関係なくタップ領域が48のままになるよう、IconButtonを優先してください。48未満に縮小するIconButton.constraintsやコンパクトなVisualDensityを指定しないでください。 - アイコンのみの
IconButton/FAB:tooltipには短いアクション名を設定します。カスタムアイコンのタップ:Semantics(label: ..., button: true)を設定します。装飾的なIcon:semanticLabelを設定します。意味のあるImage:semanticLabelを設定します。装飾的なImage:excludeFromSemantics: trueを設定します。
テキストの拡大率
MediaQuery.textScalerOf(context)で取得し、textScaler.scale(fontSize)でサイズを計算します。非推奨で、Android 14の非線形拡大率を表現できないtextScaleFactorは使用しないでください。TextはOSのフォントサイズ設定をすでに反映します。すべての画面を、小型デバイスでOSのテキストサイズを最大にしてテストしてください。アイコンフォントの拡大を止める場合に限り、MediaQuery.withNoTextScalingを使用します。MaterialAppのルートで拡大を無効にしないでください。
MediaQueryフラグ
MediaQuery.ofではなく、MediaQuery.accessibleNavigationOf、highContrastOf、disableAnimationsOf、textScalerOfを呼び出します。accessibleNavigationOfがtrueの場合、タイムアウトを無効にするか、最短時間を延長します。highContrastOfがtrueの場合、MaterialApp.highContrastThemeとhighContrastDarkThemeを設定します。サブツリーをMediaQuery(data: copyWith(highContrast: true))で囲んでも、これらのテーマには切り替わりません。disableAnimationsOfはAndroidの「アニメーションを削除」に対応します。AnimationControllerはすでにSemanticsBinding.disableAnimationsを反映します。カスタムの明示的なアニメーションでは、引き続きdisableAnimationsOfを読み取る必要があります。iOSの「視差効果を減らす」はAccessibilityFeatures.reduceMotionであり、disableAnimationsは設定しません。
フォーカス移動
- TalkBack/VoiceOverの兄弟ノードの順序:
Semantics(sortKey: OrdinalSortKey(n))を設定します。キーボード/Tabの順序は別のツリーです:FocusTraversalGroup/FocusTraversalOrderを使用します。Tabの順序を修正するためにsortKeyを使用しないでください。
コントラスト
- テキスト/コントロールと背景のコントラスト比は4.5:1以上にします(無効状態を除く)。大きなテキスト(通常の太さで18pt以上、または太字で14pt以上)は3.0:1以上にします。状態を色だけで伝えないでください。
テストとデバイス
tester.ensureSemantics()を呼び出し、次にexpectLater(tester, meetsGuideline(...))を実行し、その後handle.dispose()を呼び出します。ガイドライン:androidTapTargetGuideline、iOSTapTargetGuideline、labeledTapTargetGuideline、textContrastGuideline。- 変更した画面をすべてTalkBackとVoiceOverで確認します。Flutter web:
main()でSemanticsBinding.instance.ensureSemantics()を呼び出します。showSemanticsDebuggerまたはflutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=trueを使ってデバッグします。
サンプル
Semantics(
button: true,
label: 'Submit',
hint: 'Sends the form',
onTap: onSubmit,
child: CustomPaint(painter: SubmitPainter()),
)
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: onSearch,
)
final TextScaler scaler = MediaQuery.textScalerOf(context);
final bool screenReader = MediaQuery.accessibleNavigationOf(context);
しないこと
- すでにセマンティクスが設定されている Material/Cupertino ウィジェットを、表示テキストを繰り返す
Semantics(label:)で囲まないでください。 - インタラクティブなコンテンツを
ExcludeSemanticsで囲まないでください。また、ダイアログの背後にあるウィジェットを隠すためにBlockSemanticsの代わりに使わないでください。 - 2 つのチェックボックス、またはタップ可能な子ウィジェット 2 つに
MergeSemanticsを適用しないでください。 tooltip、Semantics.label、Icon.semanticLabelのいずれもない、アイコンのみのコントロールを使わないでください。MediaQuery.of(context).textScaleFactorまたはText(textScaleFactor:)を使わないでください。- アプリのルートでテキストの拡大を無効にしたり、
IconButtonを 48 未満に縮小したりしないでください。 MediaQuery(data: copyWith(disableAnimations: true))でAnimationControllerが停止すると想定しないでください。- ロールをラベルに含めないでください(
label: 'Submit button')。 - キーボードの Tab 順を修正するために
Semantics.sortKeyを使わないでください。
画面を修正する
- 既存のアクセシビリティの規約を確認し、それに従ってください。
- 標準の Material/Cupertino ウィジェットにはラベルを付けず、カスタム描画またはアイコンのみの場合に限って付けてください。
- アイコンのみのコントロールすべてに
tooltipまたはSemantics(label:, button: true)を設定してください。グリフではなく、タップ領域を 48 にしてください。 MediaQuery.*Ofを読み取ってください。最大の文字サイズ、ハイコントラストのテーマ、スクリーンリーダーのタイムアウト、モーションの低減に対応してください。- ガイドラインテストを追加してください。TalkBack と VoiceOver で確認してください。
検証する
- 変更した画面で
tester.ensureSemantics()、4 つのmeetsGuidelineチェック、handle.dispose()を実行してください。 - 小型デバイスで、OS の最大文字サイズでもレイアウトが収まることを確認してください。
- コントラスト比を 4.5:1 以上(大きなテキストは 3:1 以上)にしてください。状態を色だけで示さないでください。
- TalkBack と VoiceOver で、すべてのコントロールにわかりやすい名前と適切なロールがあることを確認してください。
- Web:
main()からセマンティクスを有効にしてください。