Focus Orderとは、キーボード、Switch Control、VoiceOver、TalkBackを使用してナビゲートする際に、インターフェース要素がフォーカスを受け取る順序です。モバイルアプリケーションでは、フォーカス順序によって、ユーザーがジェスチャーやボタンを使用してコントロール間を移動する方法が決まります。W3C WCAG 2.2、Success Criterion 2.4.3、2023によると、フォーカスはコンテンツの意味を保持する論理的な順序に従う必要があります。この原則の違反は、アクセシビリティ監査が不合格になる一般的な原因の一つです。
重要なポイント
Focus Orderとは、ユーザーが代替入力方法(キーボード(Tab)、Switch Control(ステップバイステップ)、VoiceOver(右/左スワイプ)、TalkBack)を使用してインタラクティブ要素間を移動する順序です。マウスやタッチスクリーンとは異なり、ユーザーが直接要素を選択するのではなく、フォーカスナビゲーションは線形的で、各ステップで次の要素にフォーカスが移動します。
Apple HIG、2024によると、VoiceOverはアクセシビリティツリー内の要素の順序を使用します。これは視覚的な配置(左上隅→右下隅)に基づいて構築されます。画面に複雑なレイアウト(列、Grid、ZStack)がある場合、ツリーが視覚的な順序と一致しない可能性があります。
WCAG 2.4.3の原則:“ウェブページをセクションごとに順次ナビゲートでき、フォーカス順序が意味に影響を与える場合、フォーカスは意味と操作性を保持する順序に従わなければならない”。例外:注意を引くためにフォーカスがジャンプする可能性のある動的コンテンツ(アラート、モーダルウィンドウ)。
Switch Controlユーザー(運動障害を持つ人々)は、自動的に要素間を移動します(サイクルを繰り返します)。順序が壊れていると、ユーザーはフォームの完了に3倍の時間がかかります。Deque University、2024によると、正しいFocus Orderは、支援技術ユーザーのフォーム完了時間を60%削減します。
特別な注意が必要なのはモーダルウィンドウです。モーダルを開いた後、フォーカスはモーダル内の最初のインタラクティブ要素(通常は“閉じる”または“確認”ボタン)に直ちに移動する必要があります。閉じた後は、モーダルをトリガーした要素に戻る必要があります。これはWCAG 2.4.3の要件であり、同時によくある間違いでもあります。
iOSでは、VoiceOverがジオメトリに基づいて自動的に順序を構築します。要素はYでソートされ、次にXでソートされます。複雑な構造の画面では、この順序が正しくない場合があり、開発者が介入する必要があります。
主なツール:
商品カードのカスタム順序設定の例:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
アクション後のプログラムによるフォーカス移動:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
shouldGroupAccessibilityElementプロパティは、コレクション内のカードに便利です。親カードでtrueに設定すると、VoiceOverはカード全体を1つの要素として認識します。ユーザーはダブルタップでカード全体をアクティブにするか、内部ナビゲーション用にローターを設定できます。UICollectionViewCellおよびUITableViewCellに推奨されます。
Androidでは、TalkBackも幾何学的な順序を使用しますが、明示的なnextFocus*属性が優先されます。これらの属性はXMLまたはプログラムで設定されます:
| 属性 | 目的 | 例 |
|---|---|---|
| nextFocusDown | 下にナビゲートするときの要素 | @+id/field_email |
| nextFocusUp | 上にナビゲートするときの要素 | @+id/field_name |
| nextFocusLeft | 左の要素 | @+id/btn_back |
| nextFocusRight | 右の要素 | @+id/btn_next |
登録フォームの例:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
RecyclerViewの場合、フォーカス順序は動的で、アダプターによって決定されます。セルの構造が複雑な場合は、descendantFocusability = “beforeDescendants”を設定し、リストアイテムノードで順序を定義します。Jetpack Composeの場合、フォーカス順序はModifier.focusOrder()とFocusOrderで設定します。優先順位:previous(子)、next(次)、カスタムキー。
要素がフォーカスに小さすぎる場合(44pt未満)、iOSのTouchDelegateまたはAndroidのminWidth/minHeightを使用してヒット領域を拡大します。Google Material Design、2024によると、最小タッチ領域は48×48dpです。VoiceOverとTalkBackは要素のバウンディングボックスにフォーカスします。30pt未満の要素はジェスチャーフォーカスにアクセスできない可能性があります。ユーザーは物理的にそれらをタップできません。
ジャンプするフォーカス — アクション(要素の削除など)の後、フォーカスがリストの先頭またはシステムの“戻る”ボタンに移動します。VoiceOverユーザーはコンテキストを失います。解決策:削除された要素に最も近い要素にプログラムでフォーカスを移動します。
非表示のフォーカス — 要素がフォーカスを受け取っても視覚的なインジケーターがありません(キーボードユーザーは現在位置がわかりません)。iOSでは、カスタムインジケーターのためにUIAccessibility.isVoiceOverRunningを確認します。Deque University、2024によると、非表示のフォーカスはアクセシビリティ監査不合格の2番目に多い原因です。
モーダル — モーダルを開いた後もフォーカスが背景コンテンツに残ります。iOSでは、modalPresentationStyle = .pageSheetが設定されている場合、モーダルビューが自動的にフォーカスを取得します。Androidでは、ダイアログコンテナにsetFocusable(true)を使用します。
逆の問題:フォーカスがモーダル内に閉じ込められ、閉じる以外に抜け出せません。これはモーダルウィンドウのみ許容されます。ユーザーは意図的にウィンドウを閉じる必要があります。通常の画面では、フォーカストラップは重大なエラーです。解決策:モーダルの最後の要素(“閉じる”ボタン)がフォーカスを戻すことを確認します。
カスタム画面(マップ、キャンバス、ゲーム)では、自動的な幾何学的順序は適用できません。開発者はアクセシビリティツリーを手動で構築する必要があります。iOSでは、このためにUIAccessibilityContainerメソッドがオーバーライドされます。
カスタムキャンバスの例:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Zインデックスで図形を並べ替え、ジオメトリではない
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
Androidでは、カスタムViewに対してonInitializeAccessibilityNodeInfoをオーバーライドします:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
動的リスト(チャット、ニュースフィード)の場合、要素を追加した後、最初の新しい要素にフォーカスを移動します。iOS:UIAccessibility.post(notification: .layoutChanged, argument: newMessage)。Android:sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)。
iOSは要素のフレームに基づいて自動的にフォーカス領域を決定します。要素に変換(transform、rotation)がある場合、VoiceOverが間違った領域にフォーカスする可能性があります。画面座標でaccessibilityFrameを明示的に設定します:element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element)。これにより、VoiceOverが正しい領域を強調表示することが保証されます。
アニメーション画面(UIKit Dynamics、Lottie、SpriteKit)の場合、プログラムによるフォーカスが特に重要です。VoiceOverは動的に移動する要素のアクセシビリティツリーを構築できません。アニメーションコンテナではisAccessibilityElement = falseを設定し、内部のインタラクティブ要素のみtrueを設定します。
手動テスト:VoiceOver(iOS)またはTalkBack(Android)を有効にし、シーケンス全体を右にスワイプします。フォーカスは視覚的な順序(左から右、上から下)に従う必要があります。各インタラクティブ要素は正確に1回フォーカスを受け取る必要があります。
自動テストは困難ですが可能です:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — ハードウェアキーボードのみ
}
Androidの場合は、Accessibility Testing Frameworkを使用します:
@Test
fun testFocusOrder() {
onView(withId(R.id.fieldEmail))
.check(matches(isFocusable()))
onView(withId(R.id.fieldEmail))
.perform(focus())
onView(withId(R.id.fieldPassword))
.check(matches(isFocused()))
}
最も信頼性の高い方法はUIシナリオテストです:フォームを段階的に入力し(メール→パスワード→送信)、各ステップが正常に完了することを確認します。フォーカス順序が壊れている場合、フォーカス外の要素と対話しようとするとシナリオは失敗します。
XcodeのAccessibility Inspectorツールは、完全なアクセシビリティツリーを表示します。VoiceOverの順序で要素を移動し、正確なフォーカスパスを確認できます。“Audit”タブを使用して、Focus Order違反を自動的に検出します。
よくある質問
WCAG 2.4.3(Focus Order)はレベルAの成功基準です。順次ナビゲーション中にフォーカス順序がコンテンツの意味を保持することを要求します。違反は重大と見なされ、認証をブロックします。
非表示の要素は、iOSではisAccessibilityElement = false、Androidではvisibility = gone/invisibleにする必要があります。表示されたら、UIAccessibility.post(notification: .layoutChanged)を使用してプログラムでフォーカスを移動します。
iOSはaccessibilityElementsとshouldGroupAccessibilityElementで管理し、AndroidはnextFocus*属性とAccessibilityNodeInfoで管理します。原則は同じです:デフォルトでは幾何学的順序で、上書きが可能です。
ルート要素にdescendantFocusability = “beforeDescendants”を設定し、各セルのonInitializeAccessibilityNodeInfoを介してアダプターで順序を設定します。
BluetoothまたはUSB経由でハードウェアキーボードを接続します。iOSでTabを押してフォーカスを移動します。AndroidでTalkBackを有効にし、Tabキーと矢印キーを使用します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。