React NativeのText#accessibleとは?エラー解決と代替手段

2025-06-06

React NativeにおけるTextコンポーネントのaccessibleプロパティは、主にアクセシビリティ(accessibility)を向上させるために使用されます。これは、視覚障がいを持つユーザーなどがスクリーンリーダー(VoiceOverやTalkBackなど)を使ってアプリを操作する際に、情報が正しく伝わるようにするための重要な機能です。

Text#accessibleとは何か?

accessibleプロパティをtrueに設定すると、そのTextコンポーネントがアクセシビリティ要素として扱われるようになります。

具体的には、以下の効果があります。

  1. スクリーンリーダーによる認識: スクリーンリーダーがそのTextコンポーネントを独立した要素として認識し、読み上げの対象とします。

なぜ必要なのか?

デフォルトでは、Textコンポーネントは、中にテキストが入っていればスクリーンリーダーによって読み上げられることがあります。しかし、複雑なUIや複数のText要素が組み合わさっている場合、そのままではスクリーンリーダーが意図しない読み上げ方をしてしまったり、ユーザーが要素をタップして操作しようとしたときに混乱したりする可能性があります。

accessible={true}を使用することで、開発者が意図する形で要素をグループ化し、スクリーンリーダーに正しい情報を伝えることができます。

使用例と関連プロパティ

通常、accessible={true}を設定する際には、以下のプロパティと組み合わせて使用することが推奨されます。

  • accessibilityHint:

    • 要素を操作したときに何が起こるかについての追加情報を提供します。accessibilityLabelだけでは説明が不十分な場合に使用します。
    <Text
      accessible={true}
      accessibilityLabel="画像を拡大"
      accessibilityHint="画像をタップすると、全画面表示になります"
    >
      拡大アイコン
    </Text>
    
  • accessibilityRole:

    • その要素がどのような役割を持つかをスクリーンリーダーに伝えます(例: "button", "header", "link"など)。
    • これにより、スクリーンリーダーは要素のテキストだけでなく、その機能もユーザーに伝えることができます。
    <Text accessible={true} accessibilityRole="button" onPress={() => { /* 処理 */ }}>
      購入する
    </Text>
    
  • accessibilityLabel:

    • スクリーンリーダーがその要素を読み上げる際に使用するテキストを設定します。
    • accessible={true}にした場合、デフォルトでは内部のテキストが連結されて読み上げられますが、accessibilityLabelを設定することで、より分かりやすい説明を提供できます。特にアイコンのみのボタンなど、視覚情報が少ない要素で重要です。
    <Text accessible={true} accessibilityLabel="詳細を見るボタン">
      詳細を見る
    </Text>
    
  • ネストされたTextaccessible:
    • <View accessible={true}> <Text>Apple</Text> <Text>Orange</Text> </View>のようにViewaccessible={true}を設定し、その中に複数のTextがある場合、スクリーンリーダーはView全体を1つのコンポーネントとして扱います。もしaccessibilityLabelが設定されていればそれが読み上げられ、設定されていなければ子要素のテキストが連結して読み上げられます。
    • 個々のText要素を独立して読み上げさせたい場合は、親のViewaccessible={true}を設定せず、個々のText要素に必要に応じてaccessible={true}を設定します。
  • タッチ可能な要素のデフォルト挙動: TouchableOpacityButtonなどのタッチ可能なコンポーネントは、デフォルトでaccessible={true}が設定されています。明示的に設定する必要はありません。


よくあるエラーと問題点

    • エラーの状況: accessible={true}を設定したにもかかわらず、その要素の目的や内容がaccessibilityLabelで明確に説明されていない場合、スクリーンリーダーのユーザーにとって分かりにくい情報になってしまいます。特に、視覚的な情報(アイコンなど)に依存する要素で起こりやすいです。
    • : アイコンのみのボタンにaccessible={true}を設定したが、accessibilityLabelがない場合、スクリーンリーダーは「ボタン」としか読み上げず、何のためのボタンか分かりません。
  1. タッチできない要素にaccessible={true}を設定してしまった

    • エラーの状況: Textコンポーネント自体にonPressなどのインタラクションがないにもかかわらず、accessible={true}を設定してしまうと、スクリーンリーダーのユーザーがその要素を「操作できるもの」と誤解してしまう可能性があります。
    • : 単なる見出しのTextaccessible={true}を設定すると、スクリーンリーダーがそれをタップできる要素と認識し、ユーザーを混乱させる可能性があります。
  2. 動的に表示される要素がスクリーンリーダーに認識されない

    • エラーの状況: アニメーションやユーザーのアクションによって動的に表示されるTextコンポーネントが、スクリーンリーダーによってすぐに認識されないことがあります。
    • 原因: スクリーンリーダーはUIの変更をすぐに検知しない場合があるためです。
  3. Text内のネストされたインタラクティブ要素が機能しない

    • エラーの状況: <Text><Text onPress={...}>リンク</Text></Text>のように、Textコンポーネントの中に別のTextコンポーネントをネストし、内側のTextonPressなどのインタラクションやaccessible={true}を設定しても、スクリーンリーダーがその内側の要素を個別に認識したり、インタラクションが有効にならなかったりする場合があります。
    • 原因: React NativeのTextコンポーネントは、内部でテキストをフラット化する処理を行うため、ネストされたインタラクティブな要素のアクセシビリティプロパティが正しく伝わらないことがあります。これは特にリンクなどで顕著です。
  4. accessible={false}が効かない(特定の状況下)

    • エラーの状況: アクセシビリティツリーから除外したい要素にaccessible={false}を設定しても、スクリーンリーダーが読み上げてしまうことがあります。
    • 原因: 親要素がaccessible={true}である場合、子要素のaccessible={false}が無視されることがあります。

トラブルシューティング

上記の問題に対する一般的なトラブルシューティング方法です。

  1. 子要素のグループ化を解除したい場合

    • 解決策: グループ化したい親要素にのみaccessible={true}を設定し、子要素にはaccessible={true}を設定しないか、必要に応じて個々のText要素にaccessible={true}accessibilityLabelを個別に設定します。
    • : 個々のテキストを独立して読み上げさせたい場合
      <View> {/* 親Viewにはaccessibleを設定しない */}
        <Text accessible={true} accessibilityLabel="最初の項目">最初の項目</Text>
        <Text accessible={true} accessibilityLabel="次の項目">次の項目</Text>
      </View>
      
    • ヒント: アクセシビリティツリーを視覚的に確認できる開発者ツール(例: iOSのAccessibility Inspector、AndroidのLayout Inspector)を使用すると、どの要素がどのようにグループ化されているか把握しやすくなります。
  2. accessibilityLabelの適切な設定

    • 解決策:
      • 常に要素の目的内容を明確に説明するテキストを設定します。
      • 視覚情報に頼らず、テキストだけで要素が何を意味するかを理解できるようにします。
      • アイコンのみのボタンには、「検索ボタン」「設定アイコン」のように、そのアイコンが何を表しているかを具体的に記述します。
    • :
      {/* 不適切: アイコンの意味が伝わらない */}
      <Text accessible={true}></Text> 
      
      {/* 適切: スクリーンリーダーに「検索」と読み上げられる */}
      <Text accessible={true} accessibilityLabel="検索ボタン"></Text> 
      
  3. タッチできない要素にはaccessible={true}を設定しない

    • 解決策: 単なる静的なテキストや装飾的な要素には、accessible={true}を設定しないでください。アクセシビリティツリーをシンプルに保ち、ユーザーの混乱を防ぎます。
  4. 動的に表示される要素への対応

    • 解決策:
      • accessibilityLiveRegionプロパティを使用します。これにより、要素の変更がスクリーンリーダーに通知されます。"polite"(現在読み上げている内容が終了してから通知)や"assertive"(即座に通知)を指定します。
      • AccessibilityInfo.announceForAccessibility() を使用して、明示的にスクリーンリーダーに内容を読み上げさせます。これは、トーストメッセージやエラーメッセージなど、ユーザーに即座に伝えたい情報に特に有効です。
    • :
      // エラーメッセージの表示と同時に読み上げ
      <Text accessibilityLiveRegion="polite">
        {errorMessage ? errorMessage : ''}
      </Text>
      
  5. Text内のネストされたインタラクティブ要素

    • 解決策:
      • 推奨される方法: Textコンポーネント内にインタラクティブな要素(リンクなど)をネストするのではなく、それらを独立したコンポーネントとして配置し、flexDirection: 'row'などを使用して視覚的に横並びに見せるようにします。
      • どうしてもText内でリンクのように振る舞わせたい場合は、リンク部分をPressableTouchableOpacityで囲み、その中にTextを入れることで、個別にインタラクティブな要素として認識させることができます。ただし、視覚的なレイアウトとアクセシビリティツリーの整合性には注意が必要です。
    • 例(非推奨のパターンと推奨される代替案):
      {/* 非推奨: スクリーンリーダーがリンクを認識しにくい */}
      <Text>
        利用規約に同意し、
        <Text onPress={() => { /* リンク処理 */ }} accessible={true} accessibilityRole="link" accessibilityLabel="利用規約">
          利用規約
        </Text>
        をご確認ください。
      </Text>
      
      {/* 推奨される代替案: 各要素が独立したアクセシビリティ要素として認識される */}
      <View style={{ flexDirection: 'row', flexWrap: 'wrap' }}>
        <Text>利用規約に同意し、</Text>
        <TouchableOpacity accessible={true} accessibilityRole="link" accessibilityLabel="利用規約">
          <Text style={{ color: 'blue', textDecorationLine: 'underline' }}>利用規約</Text>
        </TouchableOpacity>
        <Text>をご確認ください。</Text>
      </View>
      
  6. accessible={false}が効かない場合

    • 解決策: 親要素がaccessible={true}になっている場合、その子要素は強制的にグループ化され、個別のaccessible={false}は無視されることがあります。この場合、親要素のaccessible設定を見直す必要があります。本当にその要素をグループ化したいのか、それとも個々の要素を制御したいのかを明確にします。
    • ヒント: aria-hiddenに相当するaccessibilityElementsHidden (iOSのみ) や importantForAccessibility (Androidのみ) も検討できます。これらは要素全体をアクセシビリティツリーから除外する際に使用します。
  • React Nativeの公式ドキュメントを参照する: 最新のアクセシビリティに関する情報やベストプラクティスは、React Nativeの公式ドキュメントで常に確認するようにしてください。
  • シンプルさを心がける: アクセシビリティツリーはできるだけシンプルに保ち、不必要なaccessible={true}の設定は避けます。
  • デザイン段階からアクセシビリティを考慮する: アクセシビリティは開発の最終段階で追加するものではなく、UI/UXデザインの初期段階から考慮することで、後からの手戻りを減らすことができます。
  • 実際のデバイスでテストする: エミュレーターやシミュレーターだけでなく、実際のデバイスでスクリーンリーダー(iOSならVoiceOver、AndroidならTalkBack)をオンにして、アプリを操作し、どのように読み上げられ、フォーカスされるかを確認することが最も重要です。


accessible={true}: 要素をアクセシビリティ要素としてグループ化する

Text コンポーネントに accessible={true} を設定すると、その Text とその子要素がスクリーンリーダーにとって単一のフォーカス可能な要素として扱われます。これは、複数の Text が組み合わさって一つの意味を持つ場合に特に役立ちます。

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const GroupedTextExample = () => {
  return (
    <View style={styles.container}>
      {/* accessible={true} を設定することで、以下の3つのTextがまとめて読み上げられます */}
      <Text accessible={true} style={styles.groupedText}>
        <Text>これは</Text>
        <Text>アクセシビリティの</Text>
        <Text>テストです。</Text>
      </Text>

      {/* accessible={true} を設定しない場合、個別に読み上げられる可能性があります */}
      <View style={styles.ungroupedTextContainer}>
        <Text>これは</Text>
        <Text>個別の</Text>
        <Text>テキストです。</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  groupedText: {
    fontSize: 18,
    marginBottom: 20,
    borderWidth: 1,
    borderColor: 'blue',
    padding: 10,
  },
  ungroupedTextContainer: {
    flexDirection: 'row', // 横並びにするためにViewで囲む
    borderWidth: 1,
    borderColor: 'green',
    padding: 10,
  },
});

export default GroupedTextExample;

解説:

  • ungroupedTextContainer の例では、各 Text が個別に読み上げられる可能性があります(プラットフォームやスクリーンリーダーの設定に依存します)。
  • groupedText の例では、外側の Textaccessible={true} が設定されているため、スクリーンリーダーは「これはアクセシビリティのテストです。」と一つのまとまりとして読み上げます。

accessibilityLabel: スクリーンリーダーの読み上げ内容を指定する

accessible={true} と共に最もよく使われるのが accessibilityLabel です。これにより、要素の実際のテキストや内容とは別に、スクリーンリーダーに読み上げさせたいテキストを指定できます。特に、アイコンや短いテキストで意味を伝える要素に重要です。

例2: accessibilityLabel の使用

import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons'; // 例としてアイコンライブラリを使用

const AccessibleLabelExample = () => {
  return (
    <View style={styles.container}>
      {/* アイコンボタンにaccessibilityLabelを設定 */}
      <TouchableOpacity
        style={styles.iconButton}
        onPress={() => alert('設定画面へ')}
        accessible={true}
        accessibilityLabel="設定を開くボタン" // スクリーンリーダーに読み上げさせたい内容
        accessibilityRole="button" // 要素の役割を指定
      >
        <MaterialIcons name="settings" size={24} color="black" />
      </TouchableOpacity>

      {/* 通常のテキストボタン */}
      <TouchableOpacity
        style={styles.button}
        onPress={() => alert('送信しました')}
        accessible={true} // TouchableOpacityはデフォルトでtrueですが、明示的に記述
        accessibilityLabel="データを送信する"
        accessibilityRole="button"
      >
        <Text style={styles.buttonText}>送信</Text>
      </TouchableOpacity>

      {/* 不適切な例: accessibilityLabel がないアイコン */}
      <TouchableOpacity
        style={styles.iconButton}
        onPress={() => alert('詳細')}
        accessible={true}
        // accessibilityLabel がないため、スクリーンリーダーはアイコンの名称を読み上げたり、何も読み上げなかったりする可能性
      >
        <MaterialIcons name="info" size={24} color="gray" />
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  iconButton: {
    padding: 15,
    borderRadius: 5,
    backgroundColor: '#e0e0e0',
    marginBottom: 20,
  },
  button: {
    paddingVertical: 10,
    paddingHorizontal: 20,
    backgroundColor: '#007bff',
    borderRadius: 5,
    marginBottom: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
  },
});

export default AccessibleLabelExample;

解説:

  • TouchableOpacity はデフォルトで accessible={true} なので、明示的に設定しなくてもアクセシブルです。
  • 最初の TouchableOpacity では、アイコンのみで視覚的には設定ボタンとわかりますが、accessibilityLabel="設定を開くボタン" を設定することで、スクリーンリーダーのユーザーにもその目的が明確に伝わります。accessibilityRole="button" は、これがボタンであることを支援技術に伝えます。

accessibilityRole: 要素の役割を伝える

accessibilityRole は、要素がユーザーにとってどのような役割を持つかを支援技術に伝えます。これにより、スクリーンリーダーは単にテキストを読み上げるだけでなく、その要素の機能(ボタン、ヘッダー、リンクなど)も伝えます。

例3: accessibilityRole の使用

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const AccessibleRoleExample = () => {
  return (
    <View style={styles.container}>
      {/* 見出し */}
      <Text accessible={true} accessibilityRole="header" style={styles.header}>
        今日のニュース
      </Text>

      {/* リンクに見えるテキスト */}
      <Text
        accessible={true}
        accessibilityRole="link"
        accessibilityLabel="ウェブサイトへ移動"
        onPress={() => alert('ウェブサイトへ移動')}
        style={styles.linkText}
      >
        詳しくはこちら
      </Text>

      {/* 状態を示すテキスト */}
      <Text accessible={true} accessibilityRole="alert" style={styles.alertText}>
        データの読み込みに失敗しました。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  linkText: {
    fontSize: 16,
    color: 'blue',
    textDecorationLine: 'underline',
    marginBottom: 20,
  },
  alertText: {
    fontSize: 16,
    color: 'red',
    fontWeight: 'bold',
  },
});

export default AccessibleRoleExample;

解説:

  • alertText の例では、accessibilityRole="alert" を設定することで、重要な通知であることをスクリーンリーダーに伝え、即座に読み上げられる可能性があります。
  • linkText の例では、accessibilityRole="link" を設定することで、これがリンクであることを伝えます。
  • header の例では、視覚的なスタイルだけでなく、accessibilityRole="header" を設定することで、スクリーンリーダーがそれを「見出し」として認識し、ユーザーは見出しジャンプなどの機能を利用できます。

accessibilityHint は、要素の操作によって何が起こるかについての追加情報を提供します。これは、accessibilityLabel だけでは説明が不十分な場合に役立ちます。

例4: accessibilityHint の使用

import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';

const AccessibleHintExample = () => {
  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.button}
        onPress={() => alert('プロフィールを編集しました')}
        accessible={true}
        accessibilityLabel="プロフィール編集ボタン"
        accessibilityHint="プロフィール情報を変更するには、ダブルタップしてください" // 追加情報
        accessibilityRole="button"
      >
        <Text style={styles.buttonText}>プロフィールを編集</Text>
      </TouchableOpacity>

      <TouchableOpacity
        style={styles.button}
        onPress={() => alert('ログアウトしました')}
        accessible={true}
        accessibilityLabel="ログアウトボタン"
        accessibilityHint="アプリケーションからサインアウトします"
        accessibilityRole="button"
      >
        <Text style={styles.buttonText}>ログアウト</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  button: {
    paddingVertical: 10,
    paddingHorizontal: 20,
    backgroundColor: '#4CAF50',
    borderRadius: 5,
    marginBottom: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
  },
});

export default AccessibleHintExample;

解説:

  • accessibilityHint は、ユーザーが要素を操作する前にその結果を予測するのに役立つ情報を提供します。


タッチ可能なコンポーネントの使用(Text を直接タップ可能にしない)

これは Text#accessible の代替というよりも、アクセシブルなインタラクション設計のベストプラクティスです。

  • 問題点: 単純な Text コンポーネントに onPress を設定し、accessible={true} としても、スクリーンリーダーのユーザーにとっては、それが「ボタン」なのか「リンク」なのか、あるいは単なる「テキスト」なのかが不明瞭になることがあります。また、Text の子要素がネストされている場合、アクセシビリティフォーカスが期待通りに動作しないことがあります。

accessibilityLiveRegion: 動的なコンテンツの更新を通知する

Text コンポーネントのテキスト内容が動的に変化する場合、スクリーンリーダーがその変更を自動的に検知して読み上げないことがあります。

  • 代替/補完的な方法:

    • accessibilityLiveRegion を使用する: これを Text コンポーネント(またはその親の View)に設定することで、その領域内のコンテンツの変更がスクリーンリーダーに通知されるようになります。
      • "polite": 現在の読み上げが終了してから変更を通知します。
      • "assertive": 現在の読み上げを中断してすぐに変更を通知します。緊急性の高い情報(エラーなど)に適しています。

    :

    import React, { useState, useEffect } from 'react';
    import { View, Text, Button, StyleSheet } from 'react-native';
    
    const LiveRegionExample = () => {
      const [message, setMessage] = useState('');
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        const timer = setInterval(() => {
          setCount(prevCount => prevCount + 1);
        }, 1000);
        return () => clearInterval(timer);
      }, []);
    
      const showErrorMessage = () => {
        setMessage('データの読み込みに失敗しました。再試行してください。');
      };
    
      return (
        <View style={styles.container}>
          {/* エラーメッセージを動的に表示するTextに polite を設定 */}
          <Text style={styles.message} accessibilityLiveRegion="polite">
            {message}
          </Text>
          <Button title="エラーを表示" onPress={showErrorMessage} />
    
          {/* カウンターのTextに polite を設定 */}
          <Text style={styles.counter} accessibilityLiveRegion="polite">
            現在のカウント: {count}
          </Text>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
      },
      message: {
        color: 'red',
        fontSize: 16,
        marginBottom: 20,
        textAlign: 'center',
      },
      counter: {
        fontSize: 20,
        fontWeight: 'bold',
        marginTop: 20,
      },
    });
    
    export default LiveRegionExample;
    
  • 問題点: エラーメッセージ、タイマー、進捗状況など、ユーザーのアクションや時間の経過によって内容が変化する Text は、スクリーンリーダーのユーザーに伝わらない可能性があります。

AccessibilityInfo.announceForAccessibility(): 強制的に読み上げさせる

即座にユーザーに伝えたい情報がある場合(例: トーストメッセージ、操作結果のフィードバックなど)に、プログラム的にスクリーンリーダーにテキストを読み上げさせることができます。

  • 代替/補完的な方法:

    • AccessibilityInfo.announceForAccessibility(text) を使用する: これは、指定したテキストをすぐにスクリーンリーダーに読み上げさせるための関数です。

    :

    import React from 'react';
    import { View, Text, Button, StyleSheet, AccessibilityInfo } from 'react-native';
    
    const AnnounceExample = () => {
      const handleSave = () => {
        // 保存処理...
        AccessibilityInfo.announceForAccessibility('変更が保存されました。'); // スクリーンリーダーに即座に通知
        alert('変更を保存しました!');
      };
    
      const handleError = () => {
        // エラー処理...
        AccessibilityInfo.announceForAccessibility('エラーが発生しました。もう一度お試しください。');
        alert('エラーが発生しました。');
      };
    
      return (
        <View style={styles.container}>
          <Button title="保存する" onPress={handleSave} />
          <Button title="エラーを発生させる" onPress={handleError} />
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
      },
    });
    
    export default AnnounceExample;
    
  • 問題点: 複雑なUIの変更や、ユーザーのフォーカスが別の場所にある場合、重要な情報がスクリーンリーダーによって見落とされてしまうことがあります。

accessibilityElementsHidden (iOS) / importantForAccessibility (Android): 要素を非表示にする

視覚的には表示されているが、スクリーンリーダーからは無視させたい要素がある場合に利用します。

  • 代替/補完的な方法:

    • accessibilityElementsHidden={true} (iOS): 指定した要素とその子孫をiOSのアクセシビリティツリーから隠します。
    • importantForAccessibility="no-hide-descendants" (Android): 指定した要素とその子孫をAndroidのアクセシビリティサービスから無視させます。

    :

    import React from 'react';
    import { View, Text, StyleSheet } from 'react-native';
    
    const HiddenElementsExample = () => {
      return (
        <View style={styles.container}>
          <Text style={styles.visibleText}>これは通常のテキストです。</Text>
    
          {/* スクリーンリーダーから隠したい要素 */}
          <View
            style={styles.hiddenContainer}
            accessibilityElementsHidden={true} // iOS
            importantForAccessibility="no-hide-descendants" // Android
          >
            <Text style={styles.hiddenText}>このテキストはスクリーンリーダーに読み上げられません。</Text>
            <Text style={styles.hiddenText}>(ただし、視覚的には表示されています)</Text>
          </View>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
      },
      visibleText: {
        fontSize: 18,
        marginBottom: 20,
      },
      hiddenContainer: {
        borderWidth: 1,
        borderColor: 'red',
        padding: 10,
      },
      hiddenText: {
        fontSize: 14,
        color: 'gray',
      },
    });
    
    export default HiddenElementsExample;
    
  • 問題点: 装飾的な要素、または重複する情報を持つ要素がスクリーンリーダーによって読み上げられると、ユーザーにとって冗長で混乱を招く可能性があります。

accessibilityState & accessibilityValue: 要素の状態と値を伝える

チェックボックスやトグル、スライダーなどの状態を持つコンポーネントや、進捗バーなどの値を持つコンポーネントのアクセシビリティを向上させます。

  • 代替/補完的な方法:

    • accessibilityState: checked, selected, disabled, busy, expanded などのブール値または文字列 (checked: 'mixed') をオブジェクトで渡して、要素の状態を伝えます。
    • accessibilityValue: min, max, now, text などのプロパティを持つオブジェクトを渡して、スライダーや進捗バーなどの現在の値を伝えます。

    :

    import React, { useState } from 'react';
    import { View, Text, Switch, Slider, StyleSheet } from 'react-native';
    
    const StateValueExample = () => {
      const [isEnabled, setIsEnabled] = useState(false);
      const toggleSwitch = () => setIsEnabled(previousState => !previousState);
      const [sliderValue, setSliderValue] = useState(50);
    
      return (
        <View style={styles.container}>
          {/* Switch の状態を伝える */}
          <View style={styles.row}>
            <Text style={styles.label}>通知を有効にする:</Text>
            <Switch
              onValueChange={toggleSwitch}
              value={isEnabled}
              accessible={true}
              accessibilityLabel="通知のオンオフを切り替える"
              accessibilityRole="switch"
              accessibilityState={{ checked: isEnabled }} // 状態を明示的に伝える
            />
          </View>
    
          {/* スライダーの値を伝える */}
          <View style={styles.row}>
            <Text style={styles.label}>音量:</Text>
            <Slider
              style={{ width: 200, height: 40 }}
              minimumValue={0}
              maximumValue={100}
              value={sliderValue}
              onValueChange={setSliderValue}
              accessible={true}
              accessibilityLabel="音量調整スライダー"
              accessibilityRole="adjustable" // 調整可能な要素であることを示す
              accessibilityValue={{
                min: 0,
                max: 100,
                now: sliderValue,
                text: `${Math.round(sliderValue)}パーセント` // テキストでの説明も可能
              }}
            />
          </View>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
      },
      row: {
        flexDirection: 'row',
        alignItems: 'center',
        marginBottom: 20,
      },
      label: {
        fontSize: 16,
        marginRight: 10,
      },
    });
    
    export default StateValueExample;
    
  • 問題点: 要素の視覚的な状態(チェックされているか、無効になっているかなど)や数値が、スクリーンリーダーのユーザーに伝わらないことがあります。

カスタムアクセシビリティアクション (accessibilityActions, onAccessibilityAction)

より複雑なインタラクションを提供し、スクリーンリーダーのユーザーが特定のジェスチャーやメニューを介して要素を操作できるようにします。

  • 代替/補完的な方法:

    • accessibilityActions でアクションのリストを定義し、onAccessibilityAction でそのアクションが実行されたときの処理を実装します。

    : (これは Text 単体で使うよりも、リストアイテムなどのコンポーネントでよく使われます)

    import React from 'react';
    import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
    
    const CustomAccessibilityActionExample = () => {
      const handleAction = (event) => {
        if (event.nativeEvent.actionName === 'delete') {
          alert('アイテムを削除しました!');
        } else if (event.nativeEvent.actionName === 'edit') {
          alert('アイテムを編集します!');
        }
      };
    
      return (
        <View style={styles.container}>
          <TouchableOpacity
            style={styles.item}
            accessible={true}
            accessibilityLabel="購入済みアイテム"
            accessibilityRole="text" // テキストとして認識させつつ、アクションを提供
            accessibilityActions={[{ name: 'delete', label: '削除' }, { name: 'edit', label: '編集' }]}
            onAccessibilityAction={handleAction}
          >
            <Text style={styles.itemText}>ミルク 1リットル</Text>
          </TouchableOpacity>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 20,
      },
      item: {
        padding: 15,
        borderWidth: 1,
        borderColor: '#ccc',
        borderRadius: 5,
        width: '80%',
        alignItems: 'center',
      },
      itemText: {
        fontSize: 18,
      },
    });
    
    export default CustomAccessibilityActionExample;
    
  • 問題点: 複雑な操作(スワイプでアイテムを削除するなど)が、スクリーンリーダーのユーザーにとって困難である場合があります。

Text#accessible の直接的な代替というよりは、より広範なアクセシビリティ向上のためのアプローチです。

  • テスト: 実際のデバイスでスクリーンリーダー(iOS: VoiceOver, Android: TalkBack)をオンにして、アプリを徹底的にテストします。これは、コードだけでは見つけられない問題を特定するのに不可欠です。また、アクセシビリティテストツール(例: Android Accessibility Scanner)も活用します。
  • フォーカスの管理: 論理的なフォーカス順序を確保し、キーボードやスクリーンリーダーでアプリをナビゲートする際に、要素が予測可能な順序でフォーカスされるようにします。
  • 意味のあるヘッダーとラベル: 各スクリーンやセクションに明確で記述的なヘッダー (accessibilityRole="header") を提供し、すべてのインタラクティブな要素に意味のある accessibilityLabel を設定します。
  • テキストスケーリングのサポート: ユーザーがシステムのフォントサイズ設定を変更しても、アプリのテキストが適切に拡大・縮小され、UIが崩れないように設計します。allowFontScaling={false} の安易な使用は避けるべきです。
  • カラーコントラストの確保: テキストと背景色の間に十分なコントラストがあることを確認します。WebAIM Contrast Checker などのツールを利用できます。