React Native アクセシビリティ徹底解説: Text#accessibilityRole の基本と活用法

2025-06-06

Text#accessibilityRole は、React Native の <Text> コンポーネントに設定できるプロパティの一つです。このプロパティは、その Text コンポーネントがアクセシビリティサービス(スクリーンリーダーなど)に対してどのような役割を持っているかを伝えます。これにより、視覚に障がいのあるユーザーがアプリケーションのコンテンツをより効果的に理解し、操作できるようになります。

簡単に言うと、スクリーンリーダーに「このテキストは何なのか」を教えるためのものです。

なぜ重要なのか?

アクセシビリティは、すべてのユーザーがアプリケーションを公平に利用できるようにするために非常に重要です。accessibilityRole を適切に設定することで、スクリーンリーダーは以下のような情報をユーザーに提供できます。

  1. 要素のタイプを認識させる
    たとえば、「これはボタンです」「これは見出しです」「これはリスト項目です」といった情報を伝えます。
  2. 操作方法のヒントを与える
    ロールに基づいて、「この要素はタップ可能です」「この要素はスワイプで操作できます」といったヒントを自動的に生成することがあります。
  3. ナビゲーションを改善する
    ユーザーはロールに基づいて特定のタイプの要素に素早くジャンプできるようになります(例:次の見出しに移動)。

設定できる主な値とそれぞれの意味

Text#accessibilityRole に設定できる主な値は以下の通りです。これらはアクセシビリティサービスに、そのテキストがどのようなUI要素として機能しているかを伝えます。

  • 'switch': スイッチ(オン/オフを切り替えるUI)のラベルとして機能するテキストに設定します。
  • 'spinbutton': スピンボタン(数値を増減させるUI)に関連するテキストに設定します。
  • 'radio': ラジオボタンのラベルとして機能するテキストに設定します。
  • 'checkbox': チェックボックスのラベルとして機能するテキストに設定します。
  • 'alert': アラートメッセージや重要な通知を示すテキストに設定します。
  • 'text': 単なる静的なテキストであることを明示的に示します。'none' と似ていますが、より明確な意図を伝えることができます。
  • 'key': キーボードのキーのような役割を持つテキストに設定します。
  • 'image': このテキストが画像の説明として機能する場合に設定します。通常、<Image> コンポーネントの accessibilityRole に設定されることが多いですが、特定の状況でテキストに設定することも可能です。
  • 'search': このテキストが検索フィールドまたは検索結果に関連するものであることを示します。
  • 'link': このテキストがリンクであることを示します。スクリーンリーダーは「リンク」と読み上げ、タップ可能であることを示唆します。
  • 'header': このテキストが見出しであることを示します。スクリーンリーダーは通常、「見出し」と読み上げ、見出しレベルを伝えることもあります。
  • 'button': このテキストがボタンとして機能することを示します。スクリーンリーダーは「ボタン」と読み上げ、タップ可能であることを示唆します。
  • 'none': (デフォルト) 特別な役割はありません。単なるテキストとして扱われます。

これらの値は、プラットフォーム(iOSとAndroid)のネイティブのアクセシビリティAPIにマッピングされます。

使用例

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

const MyScreen = () => {
  const handlePress = () => {
    alert('ボタンが押されました!');
  };

  return (
    <View style={styles.container}>
      {/* 1. 単なるテキスト (デフォルトまたは 'text' を明示) */}
      <Text style={styles.baseText}>
        これは通常のテキストです。
      </Text>

      {/* 2. 見出しとして設定 */}
      <Text style={styles.headerText} accessibilityRole="header">
        重要な情報
      </Text>

      {/* 3. ボタンとして機能するテキスト */}
      <TouchableOpacity onPress={handlePress}>
        <Text style={styles.buttonText} accessibilityRole="button">
          設定を保存
        </Text>
      </TouchableOpacity>

      {/* 4. リンクとして機能するテキスト */}
      <Text style={styles.linkText} accessibilityRole="link" onPress={() => console.log('リンクがタップされました')}>
        プライバシーポリシー
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  baseText: {
    fontSize: 16,
    marginBottom: 10,
  },
  headerText: {
    fontSize: 24,
    fontWeight: 'bold',
    marginVertical: 20,
  },
  buttonText: {
    fontSize: 18,
    color: 'white',
    backgroundColor: '#007bff',
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 5,
    marginTop: 20,
  },
  linkText: {
    fontSize: 16,
    color: '#007bff',
    textDecorationLine: 'underline',
    marginTop: 10,
  },
});

export default MyScreen;

上記の例では、

  • "プライバシーポリシー" というテキストには accessibilityRole="link" を設定し、リンクとして認識させています。
  • "設定を保存" というテキスト(実際にはTouchableOpacityの子)には accessibilityRole="button" を設定し、ボタンとして認識させています。
  • "重要な情報" というテキストには accessibilityRole="header" を設定し、スクリーンリーダーに見出しであることを伝えています。

注意点

  • テスト
    設定後は、実際のスクリーンリーダー(iOSのVoiceOverやAndroidのTalkBackなど)を使って、意図した通りに読み上げられ、操作できるかを確認することが非常に重要です。
  • ネイティブコンポーネントとの連携
    <Button><Switch> のようなネイティブコンポーネントは、既に適切な accessibilityRole が設定されていることが多いです。カスタムコンポーネントを作成する際に、Text#accessibilityRole を活用することが特に重要になります。
  • accessibilityRole と accessibilityLabel の違い
    • accessibilityRole: 要素の役割(例: ボタン、見出し、リンク)を定義します。
    • accessibilityLabel: 要素の説明(例: 「このボタンは設定を保存します」)を提供します。
    • これらは異なる目的を持ち、組み合わせて使用することでより良いアクセシビリティを実現できます。
  • 適切な要素に設定する
    accessibilityRole は、そのテキストが実際にその役割を果たしている場合にのみ設定すべきです。例えば、ただの装飾的なテキストに accessibilityRole="button" を設定すると、ユーザーに混乱を与えます。


React Native「Text#accessibilityRole」のよくあるエラーとトラブルシューティング

ロールがスクリーンリーダーによって正しく読み上げられない

問題
accessibilityRole を設定したにもかかわらず、スクリーンリーダー(iOSのVoiceOver、AndroidのTalkBackなど)が期待通りの役割を読み上げない、または単なるテキストとして読み上げる。

考えられる原因とトラブルシューティング

  • TouchableOpacity や Button の子要素
    TouchableOpacityButton のようなインタラクティブなコンポーネントの子要素として Text を使用する場合、親コンポーネントがすでにアクセシビリティの役割を持っているため、TextaccessibilityRole="button" を設定する必要はありません。むしろ、重複した情報として扱われる可能性があります。
    • トラブルシューティング
      TouchableOpacity の中に Text を配置し、それがボタンとして機能する場合は、TouchableOpacity 自体に accessibilityLabel を設定し、Text には特別な accessibilityRole を設定しないのが一般的です。
  • 不適切なロールの使用
    テキストの実際の機能と設定した accessibilityRole が一致していない場合、スクリーンリーダーが混乱することがあります。
    • トラブルシューティング
      テキストが本当にボタンとして機能するなら 'button'、見出しなら 'header' のように、実際のUIの役割に最も合致するロールを選びます。迷った場合は、デフォルトの 'none' または 'text' に戻し、accessibilityLabel で詳細な情報を提供することを検討します。
  • プラットフォーム間の差異
    accessibilityRole の一部の値は、iOSとAndroidで挙動が異なる場合があります。例えば、特定のロールが一方のプラットフォームではサポートされているが、もう一方ではそうでない、あるいは読み上げ方が微妙に違うことがあります。
    • トラブルシューティング
      両方のプラットフォームで実際にテストし、挙動の違いを把握します。場合によっては、Platform.select を使用してプラットフォームごとに異なる accessibilityRole を適用する必要があるかもしれません。

ネストされたリンクが機能しない

問題
<Text> コンポーネント内で複数の <Text> コンポーネントをネストしてリンクを作成しようとすると、スクリーンリーダーが個々のリンクとして認識しない、または操作できない。

// 悪い例:ネストされたリンク
<Text>
  これは <Text accessibilityRole="link" onPress={...}>リンク1</Text> と{' '}
  <Text accessibilityRole="link" onPress={...}>リンク2</Text> です。
</Text>

考えられる原因とトラブルシューティング

  • トラブルシューティング
    • isScreenReaderEnabled の利用
      スクリーンリーダーが有効になっている場合にのみ、表示を調整する方法が推奨されます。例えば、スクリーンリーダーが有効な場合はリンクを分離した行に表示したり、リンク部分を別途 TouchableOpacity で囲んで accessibilityLabel を設定したりします。
    • リンクの分離
      可能な限り、複数のリンクを含む文章を分割し、それぞれのリンクを独立したアクセシビリティ要素として扱えるようにします。
    • 代替手段の提供
      スクリーンリーダーユーザーのために、ネストされたリンクの代わりに、アクセスしやすい代替手段(例: 一覧表示されたリンク)を提供することも検討します。
  • React Native のテキストのフラット化
    React Native は、<Text> コンポーネントのネストされた構造を、内部的に単一の文字列としてフラット化する傾向があります。このため、ネストされた <Text> に設定された個別の onPressaccessibilityRole がアクセシビリティAPIに適切に公開されないことがあります。

ロールの値の指定ミスや非推奨の値

問題
accessibilityRole の値が間違っている、または古いバージョンのReact Nativeでは機能したが、新しいバージョンで非推奨になった。

考えられる原因とトラブルシューティング

  • React Native のバージョンによる変更
    accessibilityRole に設定できる値は、React Native のバージョンアップに伴って追加、変更、または非推奨になることがあります。
    • トラブルシューティング
      使用しているReact Nativeのバージョンに対応する公式ドキュメント(reactnative.dev/docs/accessibility)を参照し、最新の値を把握します。コンソールに警告が表示されることもよくあるので、開発中に警告メッセージを確認する習慣をつけましょう。
  • タイポ
    単純なスペルミス。
    • トラブルシューティング
      公式ドキュメントを参照し、正しいロール名を再確認します。

特定のロールが特定の状況で期待通りに動作しない

問題
例えば、accessibilityRole="radio" を設定したテキストがiOSのVoiceOverで「ラジオボタン」として認識されない、など。

考えられる原因とトラブルシューティング

  • プラットフォームの制限
    各プラットフォームのネイティブアクセシビリティAPIの制約により、React Nativeの特定の accessibilityRole が完全にサポートされていない場合があります。特に、複雑なUI要素のロールは、完全に再現するのが難しいことがあります。
    • トラブルシューティング
      • 関連するGitHub Issueを確認
        React NativeのGitHubリポジトリで、同様の問題が報告されていないか検索します。解決策や回避策が議論されている場合があります。
      • カスタムネイティブモジュール
        どうしても解決できない場合は、ネイティブコードでアクセシビリティの機能を実装し、React Nativeのブリッジを介してJavaScriptから呼び出すことを検討します。これは最後の手段ですが、特定の高度なアクセシビリティ要件を満たすために必要になることがあります。
      • accessibilityState や accessibilityValue との組み合わせ
        単に accessibilityRole を設定するだけでなく、accessibilityState(例: { checked: true })や accessibilityValue を組み合わせて、より詳細な情報をスクリーンリーダーに提供することで、認識が改善される場合があります。

accessibilityRole の設定漏れ

問題
UI要素がスクリーンリーダーにとって何の役割を持つのか不明瞭で、ユーザーが操作に迷う。

  1. 実際のデバイスでテストする
    エミュレーターやシミュレーターだけでなく、実際のスマートフォンでスクリーンリーダーを有効にしてテストすることが不可欠です。挙動が異なる場合があります。
  2. 公式ドキュメントを常に参照する
    React Nativeのアクセシビリティに関する公式ドキュメントは、最も信頼できる情報源です。定期的に更新されるため、最新の情報を確認しましょう。
  3. accessibilityLabel と accessibilityHint を活用する
    accessibilityRole が期待通りに機能しない場合や、より詳細な情報を提供したい場合は、accessibilityLabel (要素の簡潔な説明) や accessibilityHint (要素の操作方法のヒント) を組み合わせて使用します。
  4. シンプルなUIから始める
    複雑なカスタムコンポーネントを開発する前に、標準のコンポーネントで Text#accessibilityRole の挙動を理解し、徐々にカスタム化を進めます。


Text#accessibilityRole は、スクリーンリーダーなどのアクセシビリティサービスに対して、<Text> コンポーネントがアプリケーション内でどのような役割を果たしているかを伝えるためのプロパティです。これにより、視覚に障がいのあるユーザーがアプリのコンテンツをより理解し、操作できるようになります。

以下の例では、様々な accessibilityRole の使い方を示します。

例1: 基本的なテキストと見出し

最も基本的な使用例です。単なる静的なテキストと、見出しとして機能するテキストを区別します。

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

const BasicTextRoles = () => {
  return (
    <View style={styles.container}>
      {/* 1. 通常のテキスト (デフォルトでは 'none' または 'text' と解釈される) */}
      <Text style={styles.paragraph}>
        これはアプリの紹介文の一部です。
        スクリーンリーダーはこれを単なるテキストとして読み上げます。
      </Text>

      {/* 2. 見出しとして設定 */}
      <Text style={styles.header} accessibilityRole="header">
        最新情報
      </Text>
      <Text style={styles.paragraph}>
        新機能がリリースされました!詳細はこちら。
      </Text>

      {/* 3. さらに大きな見出し(レベルを示すスタイルと共に) */}
      <Text style={styles.subHeader} accessibilityRole="header">
        アカウント設定
      </Text>
      <Text style={styles.paragraph}>
        パスワードの変更やプロフィールの更新ができます。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  paragraph: {
    fontSize: 16,
    marginBottom: 15,
    textAlign: 'center',
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    marginVertical: 10,
    color: '#333',
  },
  subHeader: {
    fontSize: 20,
    fontWeight: 'bold',
    marginVertical: 10,
    color: '#555',
  },
});

export default BasicTextRoles;

解説

  • "最新情報""アカウント設定" のテキストには accessibilityRole="header" を設定しています。これにより、スクリーンリーダーはこれらのテキストを「見出し」として認識し、「見出し」と読み上げるか、見出し間のナビゲーション(例:VoiceOverの「見出しローター」)を可能にします。
  • "これはアプリの紹介文の一部です。" のような通常のテキストには accessibilityRole を明示的に設定していません。これはデフォルトで適切なテキストとして扱われます。

例2: ボタンとリンクとして機能するテキスト

テキスト自体がインタラクティブな要素として機能する場合に accessibilityRole を設定します。

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

const InteractiveTextRoles = () => {
  const handleSavePress = () => {
    Alert.alert('設定を保存', '設定が正常に保存されました。');
  };

  const handlePrivacyPolicyPress = () => {
    Alert.alert('プライバシーポリシー', 'プライバシーポリシーが開きます...');
    // 実際のアプリでは、ここにWebビューを開くなどのロジックを追加します
  };

  return (
    <View style={styles.container}>
      {/* 1. ボタンとして機能するテキスト */}
      {/* 注意: TouchableOpacity が既に accessible なので、Text には必須ではないですが、
             より明確なセマンティクスを提供するために設定できます。
             一般的には、TouchableOpacity に accessibilityLabel を設定する方が推奨されます。 */}
      <TouchableOpacity onPress={handleSavePress} style={styles.button}>
        <Text style={styles.buttonText} accessibilityRole="button">
          設定を保存
        </Text>
      </TouchableOpacity>

      {/* 2. リンクとして機能するテキスト */}
      {/* Text に onPress を直接設定すると、Androidではタップ可能と認識されない場合があります。
             そのため、TouchableOpacity で囲むか、Linking API などで処理をフックします。
             ここでは、accessibilityRole="link" を設定し、onPress イベントハンドラを追加します。 */}
      <Text
        style={styles.linkText}
        accessibilityRole="link"
        onPress={handlePrivacyPolicyPress} // TextonPress はプラットフォーム依存
      >
        プライバシーポリシー
      </Text>

      {/* 3. より確実なリンクの実装(TouchableOpacity で囲む) */}
      <TouchableOpacity onPress={handlePrivacyPolicyPress} style={{ marginTop: 20 }}>
        <Text style={styles.linkText} accessibilityRole="link">
          利用規約を読む
        </Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  button: {
    backgroundColor: '#007bff',
    paddingVertical: 12,
    paddingHorizontal: 25,
    borderRadius: 8,
    marginBottom: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
  linkText: {
    color: '#007bff',
    fontSize: 16,
    textDecorationLine: 'underline',
  },
});

export default InteractiveTextRoles;

解説

  • "利用規約を読む" の例は、TouchableOpacity で囲むことで、より確実にリンクとして機能させる方法を示しています。この場合でも TextaccessibilityRole="link" を設定することで、そのテキストがリンクであることを明示できます。
  • "プライバシーポリシー" の例では、TextaccessibilityRole="link"onPress を設定しています。これにより、スクリーンリーダーは「プライバシーポリシー、リンク」と読み上げます。Text に直接 onPress を設定する方法は、iOSでは機能しますが、Androidではタップ可能として認識されない場合があります(Touchable* コンポーネントで囲むのがより確実です)。
  • "設定を保存" の例では、TouchableOpacity で囲まれた TextaccessibilityRole="button" を設定しています。これにより、スクリーンリーダーは「設定を保存、ボタン」と読み上げ、タップ可能であることを示唆します。ただし、TouchableOpacity 自体がアクセシビリティ対応の要素なので、Text への accessibilityRole 設定は必須ではありません。TouchableOpacityaccessibilityLabel="設定を保存ボタン" のように設定する方が一般的で確実です。

例3: チェックボックスやラジオボタンのラベルとして機能するテキスト

テキストがUIの状態を表す要素のラベルである場合に利用します。

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'; // アイコンライブラリを使用

const CheckboxAndRadioRoles = () => {
  const [isChecked, setIsChecked] = useState(false);
  const [selectedOption, setSelectedOption] = useState('optionA');

  return (
    <View style={styles.container}>
      {/* チェックボックスの例 */}
      <TouchableOpacity
        onPress={() => setIsChecked(!isChecked)}
        style={styles.row}
        // 親にaccessibleを設定し、子に役割を与える
        accessibilityRole="checkbox"
        accessibilityState={{ checked: isChecked }} // チェック状態を伝える
        accessibilityLabel="通知を受け取る" // ラベルを提供
      >
        <Icon
          name={isChecked ? 'check-box' : 'check-box-outline-blank'}
          size={24}
          color="#007bff"
        />
        <Text style={styles.label}>通知を受け取る</Text>
      </TouchableOpacity>

      <Text style={styles.sectionHeader}>お支払い方法を選択</Text>

      {/* ラジオボタンの例 - オプションA */}
      <TouchableOpacity
        onPress={() => setSelectedOption('optionA')}
        style={styles.row}
        accessibilityRole="radio"
        accessibilityState={{ checked: selectedOption === 'optionA' }}
        accessibilityLabel="クレジットカード"
      >
        <Icon
          name={selectedOption === 'optionA' ? 'radio-button-checked' : 'radio-button-unchecked'}
          size={24}
          color="#007bff"
        />
        <Text style={styles.label}>クレジットカード</Text>
      </TouchableOpacity>

      {/* ラジオボタンの例 - オプションB */}
      <TouchableOpacity
        onPress={() => setSelectedOption('optionB')}
        style={styles.row}
        accessibilityRole="radio"
        accessibilityState={{ checked: selectedOption === 'optionB' }}
        accessibilityLabel="銀行振込"
      >
        <Icon
          name={selectedOption === 'optionB' ? 'radio-button-checked' : 'radio-button-unchecked'}
          size={24}
          color="#007bff"
        />
        <Text style={styles.label}>銀行振込</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'flex-start', // 左寄せ
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 15,
  },
  label: {
    fontSize: 16,
    marginLeft: 10,
  },
  sectionHeader: {
    fontSize: 18,
    fontWeight: 'bold',
    marginTop: 20,
    marginBottom: 10,
  },
});

export default CheckboxAndRadioRoles;

解説

  • accessibilityLabel で、要素が何であるかを明示的に定義しています。
  • accessibilityState を使用して、チェックボックスやラジオボタンの現在の状態(チェックされているか否か)をスクリーンリーダーに伝えています。
  • TouchableOpacityaccessibilityRole を設定し、Text は単なる視覚的なラベルとしています。これは非常に重要で、TextaccessibilityRole="checkbox" を設定するよりも、親のインタラクティブな要素(TouchableOpacity など)に設定する方が正しいアプローチです。
  • この例では、カスタムのチェックボックスとラジオボタンを作成しています。

例4: アクセシビリティのベストプラクティスを考慮した例

Text#accessibilityRole は単体で使用するだけでなく、accessibilityLabelaccessibilityHint と組み合わせて使うことで、より包括的なアクセシビリティを提供できます。

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

const AccessibilityBestPractices = () => {
  const handleProfilePress = () => {
    Alert.alert('プロフィール', 'プロフィール画面に遷移します。');
  };

  return (
    <View style={styles.container}>
      {/* ヘッダーとして機能するテキスト */}
      <Text
        style={styles.mainHeader}
        accessibilityRole="header"
        accessibilityLabel="ようこそ、アプリへ" // スクリーンリーダーが読み上げるラベル
        accessibilityHint="アプリのメイン画面です" // 追加のヒント
      >
        ようこそ!
      </Text>

      {/* ナビゲーションリンクとしてのテキスト(ボタンとして実装) */}
      <TouchableOpacity
        onPress={handleProfilePress}
        style={styles.navButton}
        accessibilityRole="button" // ボタンとしての役割
        accessibilityLabel="プロフィール" // 「プロフィール」と読み上げ
        accessibilityHint="あなたのユーザー情報を表示します" // ユーザーへの操作ヒント
      >
        <Text style={styles.navButtonText}>プロフィールを見る</Text>
      </TouchableOpacity>

      {/* ニュースのタイトル(見出しとして) */}
      <Text style={styles.newsTitle} accessibilityRole="header" accessibilityLevel={2}>
        新機能のお知らせ
      </Text>
      <Text style={styles.newsContent}>
        最新アップデートで新しいダークモードが利用可能になりました!
      </Text>

      {/* 外部リンクとしてのテキスト */}
      <Text
        style={styles.externalLink}
        accessibilityRole="link"
        onPress={() => Alert.alert('外部サイト', '公式サイトに移動します。')}
        accessibilityLabel="公式サイト"
        accessibilityHint="詳細情報を確認するために外部ウェブサイトを開きます"
      >
        公式サイトはこちら
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  mainHeader: {
    fontSize: 32,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#222',
  },
  navButton: {
    backgroundColor: '#6c757d',
    paddingVertical: 12,
    paddingHorizontal: 30,
    borderRadius: 25,
    marginBottom: 20,
  },
  navButtonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
  newsTitle: {
    fontSize: 22,
    fontWeight: 'bold',
    marginTop: 30,
    marginBottom: 10,
    color: '#444',
  },
  newsContent: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 20,
  },
  externalLink: {
    color: '#28a745',
    fontSize: 16,
    textDecorationLine: 'underline',
    marginTop: 10,
  },
});

export default AccessibilityBestPractices;
  • accessibilityLevel (iOSのみ)
    • "新機能のお知らせ" の見出しには accessibilityLevel={2} を設定しています。これはiOS (VoiceOver) でのみ有効で、見出しの階層レベル(H1, H2など)をスクリーンリーダーに伝えます。Android (TalkBack) ではこのプロパティは無視されます。
  • accessibilityRole と accessibilityLabel の組み合わせ
    • "ようこそ!" のテキストは、accessibilityRole="header" に加えて accessibilityLabel="ようこそ、アプリへ" を持っています。これにより、スクリーンリーダーは「ようこそ、アプリへ、見出し」と読み上げます。accessibilityLabel は、UI上の見た目のテキストと異なる、より分かりやすい情報を伝えるのに役立ちます。
    • "プロフィールを見る" ボタンも同様に accessibilityRole="button"accessibilityLabel="プロフィール" を組み合わせています。


Text#accessibilityRole はテキスト要素の役割を伝える上で非常に有用ですが、それだけがアクセシビリティ対応の全てではありません。React Native には、より包括的なアクセシビリティを実装するための様々なプロパティやアプローチが存在します。

accessibilityLabel の活用

accessibilityLabel は、要素の視覚的なテキストや内容とは別に、スクリーンリーダーに読み上げさせるための短い説明を提供します。これは accessibilityRole と密接に連携して機能し、多くの場合、accessibilityRole よりも重要になります。

利点

  • 短縮されたテキストや専門用語を、より分かりやすい言葉に置き換える。
  • アイコンのみのボタンなど、視覚的な情報だけでは分かりにくい要素に説明を与える。
  • 要素の目的を明確に伝える。

使用例

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

const AccessibilityLabelExample = () => {
  return (
    <View style={styles.container}>
      {/* アイコンボタンにラベルを提供 */}
      <TouchableOpacity
        onPress={() => alert('設定を開く')}
        style={styles.iconButton}
        accessibilityLabel="設定を開くボタン" // スクリーンリーダーに読み上げられる内容
        // accessibilityRole はここで "button" が推測されるため、明示不要な場合が多い
      >
        <Icon name="settings" size={30} color="#007bff" />
      </TouchableOpacity>

      {/* 視覚的なテキストとは異なるラベルを提供 */}
      <Text
        style={styles.shortText}
        accessibilityLabel="あなたのプロフィール情報" // 「プロフ」ではなく「プロフィール情報」と読み上げられる
      >
        プロフ
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  iconButton: {
    padding: 15,
    borderRadius: 50,
    backgroundColor: '#e0e0e0',
    marginBottom: 20,
  },
  shortText: {
    fontSize: 20,
    fontWeight: 'bold',
  },
});

export default AccessibilityLabelExample;

解説

  • 「プロフ」という省略されたテキストには、accessibilityLabel="あなたのプロフィール情報" を設定し、ユーザーに完全な情報を提供しています。
  • 設定アイコンのボタンには、accessibilityLabel="設定を開くボタン" を設定しています。これにより、スクリーンリーダーはアイコンの見た目に関わらず、「設定を開くボタン」と読み上げます。

accessibilityHint の活用

accessibilityHint は、要素の役割やラベルだけでは伝わらない、操作方法や次のアクションに関する追加情報を提供します。

利点

  • 複雑なインタラクションのヒントを提供する。
  • ユーザーが要素を操作する際に何が起こるかを予測させる。

使用例

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

const AccessibilityHintExample = () => {
  return (
    <View style={styles.container}>
      <TouchableOpacity
        onPress={() => Alert.alert('アカウント', 'アカウント設定が開きます。')}
        style={styles.button}
        accessibilityLabel="アカウント"
        accessibilityHint="アカウント情報を編集するためにダブルタップします" // 操作ヒント
      >
        <Text style={styles.buttonText}>アカウント</Text>
      </TouchableOpacity>

      <Text
        style={styles.draggableItem}
        accessibilityLabel="アイテムA"
        accessibilityHint="ドラッグして並べ替えることができます" // ドラッグ操作のヒント
      >
         アイテムA 
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  button: {
    backgroundColor: '#28a745',
    paddingVertical: 12,
    paddingHorizontal: 25,
    borderRadius: 8,
    marginBottom: 30,
  },
  buttonText: {
    color: 'white',
    fontSize: 18,
    fontWeight: 'bold',
  },
  draggableItem: {
    fontSize: 20,
    padding: 15,
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 5,
  },
});

export default AccessibilityHintExample;

解説

  • ドラッグ可能なアイテムには、accessibilityHint="ドラッグして並べ替えることができます" を設定し、特殊な操作方法を提示しています。
  • 「アカウント」ボタンには、accessibilityHint="アカウント情報を編集するためにダブルタップします" を設定し、ユーザーがどのように操作すれば良いかを具体的に伝えています。

accessibilityState の活用

accessibilityState は、要素の現在の状態(チェックされているか、選択されているか、無効になっているかなど)をスクリーンリーダーに伝えます。これは accessibilityRole と組み合わせて、カスタムコンポーネントの状態を正確に反映させるために非常に重要です。

利点

  • 要素が展開/折りたたみ可能であるかを伝える。
  • 要素が有効/無効であるかを伝える。
  • チェックボックス、ラジオボタン、スイッチなどの状態を伝える。

使用例

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

const AccessibilityStateExample = () => {
  const [isEnabled, setIsEnabled] = useState(false);
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <View style={styles.container}>
      {/* スイッチ(ネイティブコンポーネントだが概念は同じ) */}
      <View style={styles.row}>
        <Text style={styles.label}>通知を有効にする</Text>
        <Switch
          onValueChange={setIsEnabled}
          value={isEnabled}
          accessibilityLabel="通知のオンオフを切り替えるスイッチ"
          // Switch コンポーネントは内部で適切な rolestate を持っている
        />
      </View>

      {/* カスタム展開/折りたたみ可能なセクション */}
      <TouchableOpacity
        onPress={() => setIsExpanded(!isExpanded)}
        style={styles.collapsibleHeader}
        accessibilityRole="button" // ボタンとして操作可能
        accessibilityLabel="詳細情報"
        accessibilityState={{ expanded: isExpanded }} // 展開状態を伝える
        accessibilityHint={isExpanded ? "折りたたむにはダブルタップします" : "展開するにはダブルタップします"}
      >
        <Text style={styles.collapsibleText}>詳細情報</Text>
        <Text style={styles.collapsibleIcon}>{isExpanded ? '▲' : '▼'}</Text>
      </TouchableOpacity>
      {isExpanded && (
        <View style={styles.collapsibleContent}>
          <Text>ここに詳細なコンテンツが表示されます。</Text>
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  row: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
  },
  label: {
    fontSize: 18,
    marginRight: 10,
  },
  collapsibleHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    width: '80%',
    padding: 15,
    backgroundColor: '#e0e0e0',
    borderRadius: 8,
    marginBottom: 10,
  },
  collapsibleText: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  collapsibleIcon: {
    fontSize: 18,
  },
  collapsibleContent: {
    width: '80%',
    padding: 15,
    backgroundColor: '#f9f9f9',
    borderWidth: 1,
    borderColor: '#eee',
    borderRadius: 8,
  },
});

export default AccessibilityStateExample;

解説

accessible プロパティの制御

accessible プロパティは、要素(通常は ViewTouchableOpacity)とその子要素を、単一のアクセシビリティ要素としてグループ化するかどうかを制御します。

利点

  • カスタムコンポーネント全体を単一のインタラクティブな要素として定義する。

注意点

  • accessible={true} を設定した要素の子要素は、個別の accessibilityRoleaccessibilityLabel が無視されることがあります。この場合、親要素に適切な accessibilityLabel を設定する必要があります。

使用例

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

const AccessibleGroupExample = () => {
  return (
    <View style={styles.container}>
      {/* 複数のテキストを一つのアクセシビリティ要素としてグループ化 */}
      <TouchableOpacity
        onPress={() => alert('ユーザープロファイルを見る')}
        style={styles.profileCard}
        accessible={true} // このView全体を一つのアクセシビリティ要素にする
        accessibilityLabel="ユーザー名 ジョン・ドウ、メールアドレス [email protected]、プロフィールを見るボタン"
        accessibilityRole="button" // 全体をボタンとして扱う
      >
        <Text style={styles.profileName}>ジョン・ドウ</Text>
        <Text style={styles.profileEmail}>[email protected]</Text>
        <Text style={styles.viewProfileText}>プロフィールを見る</Text>
      </TouchableOpacity>

      {/* グループ化しない場合(冗長になる可能性) */}
      <View style={styles.profileCardAlt}>
        <Text style={styles.profileName}>ジョン・ドウ</Text> {/* 1. ジョン・ドウと読み上げ */}
        <Text style={styles.profileEmail}>[email protected]</Text> {/* 2. [email protected]と読み上げ */}
        <TouchableOpacity onPress={() => alert('ユーザープロファイルを見る')}>
          <Text style={styles.viewProfileText} accessibilityRole="button"> {/* 3. プロフィールを見る、ボタンと読み上げ */}
            プロフィールを見る
          </Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  profileCard: {
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    padding: 15,
    alignItems: 'center',
    marginBottom: 30,
  },
  profileCardAlt: {
    borderWidth: 1,
    borderColor: '#ccc',
    borderRadius: 8,
    padding: 15,
    alignItems: 'center',
    marginBottom: 30,
    backgroundColor: '#f0f0f0',
  },
  profileName: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  profileEmail: {
    fontSize: 16,
    color: '#666',
    marginBottom: 10,
  },
  viewProfileText: {
    fontSize: 16,
    color: '#007bff',
  },
});

export default AccessibleGroupExample;

解説

  • 2番目の profileCardAltaccessible={true} を設定していないため、各テキストやボタンが個別に読み上げられ、ユーザーは多くのスワイプ操作が必要になる可能性があります。
  • 最初の profileCardaccessible={true} を設定し、その中に含まれるすべてのテキストを「ユーザー名 ジョン・ドウ、メールアドレス [email protected]、プロフィールを見るボタン」という単一の塊として読み上げます。これにより、ユーザーはより効率的に情報を得られます。

importantForAccessibility の活用

importantForAccessibility は、特定の要素をアクセシビリティツリーに含めるべきか、またはスキップすべきかを制御します。(Androidのみ)

利点

  • 特定の要素を優先的にアクセシビリティツリーに含める。
  • 画面上に表示されているが、アクセシビリティユーザーには不要な要素をスキップさせる。

使用例

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

const ImportantForAccessibilityExample = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.mainText}>重要な情報はこちらです。</Text>

      {/* Androidでのみ機能 */}
      {Platform.OS === 'android' && (
        <Text
          style={styles.decorativeText}
          importantForAccessibility="no" // スクリーンリーダーに読み上げさせない
        >
          これは装飾的なテキストなので、スキップされます。
        </Text>
      )}

      {Platform.OS === 'android' && (
        <Text
          style={styles.liveRegionText}
          importantForAccessibility="yes" // 明示的に含める(デフォルト)
        >
          通常はデフォルトで含められます。
        </Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  mainText: {
    fontSize: 20,
    marginBottom: 15,
  },
  decorativeText: {
    fontSize: 14,
    color: '#888',
    fontStyle: 'italic',
  },
  liveRegionText: {
    fontSize: 16,
    marginTop: 10,
  },
});

export default ImportantForAccessibilityExample;

解説

  • importantForAccessibility="no" を設定したテキストは、Androidのスクリーンリーダーでは読み上げられません。これは、視覚的には必要だが、アクセシビリティ上は冗長な要素(例:区切り線、単純な装飾テキスト)に便利です。

Text#accessibilityRole はテキストの役割を明確にするために重要ですが、React Native のアクセシビリティAPIはこれに留まりません。

  • importantForAccessibility: 特定のプラットフォームで要素をアクセシビリティツリーに含める/含めないを制御する。
  • accessibilityState: 要素の現在の状態(チェック状態、展開状態など)を伝える。
  • accessibilityHint: 要素の操作方法や結果をヒントとして伝える。
  • accessibilityLabel: 何の要素であるかを具体的に説明する。最も汎用性が高く、重要。