アクセシブルなUIを実現するText#accessibilityActions:React Nativeプログラミング例

2025-06-06

このプロパティを使用すると、視覚障害のあるユーザーがスクリーンリーダー(VoiceOver や TalkBack など)を介して Text コンポーネントを操作する際に、カスタムのアクションを提供できます。

具体的には、accessibilityActions はオブジェクトの配列を受け取ります。各オブジェクトは以下のようなプロパティを持ちます。

  • onPress: アクションが実行されたときに呼び出される関数。この関数内で、アクションに対応する処理を記述します。
  • label: アクションのラベル(文字列)。name よりも詳細な説明を提供する場合に使用されます。ユーザーがアクションを選択する際に表示されることがあります。
  • name: アクションの名前(文字列)。スクリーンリーダーがこのアクションをユーザーにアナウンスする際に使用されます。例えば、"activate" や "toggle" など。

使用例

例えば、あるテキストをタップすると、そのテキストを「お気に入りに追加」したり、「共有」したりする機能を提供したいとします。この場合、Text#accessibilityActions を使用して、スクリーンリーダーユーザーにもこれらのアクションを提供できます。

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

const MyAccessibleText = () => {
  const handleFavorite = () => {
    Alert.alert('お気に入りに追加しました!');
  };

  const handleShare = () => {
    Alert.alert('共有アクションを実行します!');
  };

  return (
    <View style={{ padding: 20 }}>
      <Text
        accessibilityLabel="このテキストは操作可能です"
        accessibilityActions={[
          { name: 'favorite', label: 'お気に入りに追加' },
          { name: 'share', label: '共有' },
        ]}
        onAccessibilityAction={(event) => {
          switch (event.nativeEvent.actionName) {
            case 'favorite':
              handleFavorite();
              break;
            case 'share':
              handleShare();
              break;
            default:
              break;
          }
        }}
        style={{ fontSize: 18 }}
      >
        React Nativeのアクセシビリティ機能のデモです。
      </Text>
    </View>
  );
};

export default MyAccessibleText;

上記のコードの説明

  1. Text コンポーネントに accessibilityLabel を設定しています。これは、スクリーンリーダーがこのテキストを読み上げる際の代替テキストです。
  2. accessibilityActions プロパティには、2つのアクション(favoriteshare)を定義しています。
    • name: それぞれ favoriteshare
    • label: それぞれ「お気に入りに追加」と「共有」。
  3. onAccessibilityAction プロパティは、ユーザーがスクリーンリーダー経由で定義されたアクションを選択したときに呼び出されるコールバック関数です。
    • event.nativeEvent.actionName を使用して、どのカスタムアクションが実行されたかを判別し、それに対応する処理(handleFavoritehandleShare)を呼び出します。
  • WAI-ARIA(Web Accessibility Initiative - Accessible Rich Internet Applications)の概念との連携
    WebにおけるWAI-ARIAの概念に近く、ネイティブアプリでも同様のアクセシブルな操作性を提供できます。
  • ユーザーエクスペリエンスの向上
    スクリーンリーダーユーザーにとって、より豊かでインタラクティブな体験を提供できます。
  • アクセシビリティの向上
    視覚障害のあるユーザーが、単にテキストを読み上げるだけでなく、テキストに関連する操作を実行できるようになります。


onAccessibilityAction が発火しない、または期待通りに動作しない

原因

  • ネストされたアクセス可能な要素
    親要素が accessible={true} で、その中に子要素も accessible={true} を持つ場合、子要素が優先されて親要素のアクションが発火しないことがあります。特に Text の中にさらに Text がネストされている場合などに発生しやすいです。
  • name の不一致
    accessibilityActions で定義した name と、onAccessibilityActionevent.nativeEvent.actionName で比較する文字列が一致していない。タイプミスなども考えられます。
  • スクリーンリーダーでの操作方法の誤解
    スクリーンリーダーによっては、アクションを実行するための特定のジェスチャーやメニュー操作が必要です。例えば、iOS の VoiceOver では、要素にフォーカスを合わせた後、2本指で上または下にスワイプするとアクションメニューが表示されます。ユーザーがそのメニューからアクションを選択しないと onAccessibilityAction は発火しません。
  • accessible={true} の設定忘れ
    accessibilityActions を設定する要素(またはその親要素)に accessible={true} が設定されていない場合、スクリーンリーダーはその要素を単一のアクセス可能な要素として認識せず、アクションも発火しません。特に View の中に Text を配置し、View 全体でアクションを扱いたい場合に忘れがちです。

トラブルシューティング

  • 要素の階層の簡素化
    アクセシビリティを考慮する要素の階層をできるだけシンプルに保ち、不要な accessible={true} を削除することで、意図しない要素がスクリーンリーダーのフォーカスを持つことを防げます。
  • console.log でデバッグ
    onAccessibilityAction の内部で console.log(event.nativeEvent.actionName) を出力し、実際にどのような actionName が渡されているかを確認します。
  • スクリーンリーダーでのテスト
    実際にデバイス(実機またはシミュレータ)でスクリーンリーダー(iOS の VoiceOver、Android の TalkBack)を有効にして、どのように要素が読み上げられ、アクションが利用可能になっているかを確認します。アクションメニューが表示されるか、正しく読み上げられるかなどを確認しましょう。
  • accessible={true} の確認
    まず、accessibilityActions を設定したコンポーネント、またはその最も近い親のアクセス可能な要素に accessible={true} が設定されていることを確認してください。

アクションがスクリーンリーダーで正しく読み上げられない、または認識されない

原因

  • プラットフォーム間の動作の違い
    iOS と Android のスクリーンリーダーは、アクセシビリティアクションの読み上げ方や操作方法に微妙な違いがあることがあります。
  • accessibilityLabel との競合
    Text コンポーネント自体に accessibilityLabel が設定されている場合、それがアクションのラベルと混同されたり、優先されたりする可能性があります。
  • label の欠如または不明瞭さ
    accessibilityActions の各オブジェクトに label プロパティが設定されていない、またはユーザーにとって意味が分かりにくいラベルになっている。

トラブルシューティング

  • 両プラットフォームでのテスト
    iOS と Android の両方でスクリーンリーダーを有効にして、実際にユーザーがどのように情報を取得し、操作できるかを確認します。
  • 明確な label の設定
    accessibilityActions の各オブジェクトの label は、ユーザーがそのアクションを実行することで何が起こるかを明確に伝えるように記述してください。

accessibilityActions が指定できないコンポーネント

原因

  • accessibilityActions は、主に ViewTextTouchable 系のコンポーネントでサポートされています。これらのコンポーネント以外で直接 accessibilityActions を指定しようとするとエラーになるか、無視されます。

トラブルシューティング

  • ラッパーコンポーネントの利用
    もし特定のカスタムコンポーネントにアクセシビリティアクションを追加したい場合は、そのカスタムコンポーネントを ViewTouchableOpacity でラップし、そのラッパーに accessibilityActionsonAccessibilityAction を設定します。
  • サポートされているコンポーネントを使用する
    accessibilityActions を提供したい場合は、ViewText、または TouchableOpacity などの Touchable コンポーネントを使用してください。

アクションの処理が複雑すぎる、または状態管理と連携が難しい

原因

  • onAccessibilityAction 内で直接的に複雑なロジックを記述しようとすると、コードが読みにくくなったり、状態管理との連携が難しくなったりすることがあります。

トラブルシューティング

  • 状態管理ライブラリとの連携
    Redux や Context API などの状態管理ライブラリを使用している場合は、onAccessibilityAction からこれらのシステムのアクションをディスパッチすることで、アプリケーションの状態を適切に更新できます。
  • 関数の分離
    onAccessibilityAction の中では、どのどのアクションが発火したかを判別し、それぞれの具体的な処理は別の関数として分離することを推奨します。これにより、コードの可読性が向上し、テストも容易になります。

原因

  • アクセシビリティ機能は、通常のUIの見た目とは異なり、スクリーンリーダーを通して動作するため、デバッグが視覚的に難しい場合があります。
  • シンプルな例で検証
    問題が発生した場合、複雑なコンポーネントから切り離し、最小限の TextaccessibilityActions を持つシンプルなコンポーネントを作成して動作を検証すると、問題の切り分けがしやすくなります。
  • デバッグツールとログの活用
    • React Native Debugger
      通常のJavaScriptのデバッグと同様に、console.log やブレークポイントを設定して、onAccessibilityAction が呼び出されているか、正しいイベントデータが渡されているかを確認します。
    • OSのアクセシビリティ開発ツール
      • iOS (Xcode): Accessibility Inspector
        Xcode に付属する Accessibility Inspector を使用すると、iOS シミュレータまたは実機でアクセシビリティツリーを視覚的に検査できます。要素のアクセシビリティプロパティ(ラベル、ロール、ヒント、アクションなど)を詳細に確認できます。
      • Android: Accessibility Scanner / Layout Inspector
        Android には Accessibility Scanner というツールがあり、アプリのアクセシビリティ問題を自動的にスキャンしてレポートしてくれます。また、Android Studio の Layout Inspector もレイアウトのアクセシビリティ情報を確認するのに役立ちます。
  • スクリーンリーダーを常に有効にしてテスト
    開発中は、定期的にスクリーンリーダーを有効にしてテストを行う習慣をつけることが重要です。これにより、早期に問題を特定できます。


例1: 基本的なアクションの追加(お気に入り/共有)

この例では、ユーザーがスクリーンリーダーを使って特定のテキストに対して「お気に入りに追加」または「共有」のアクションを実行できるようにします。

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

const BasicTextActionsExample = () => {
  const handleFavorite = () => {
    Alert.alert('アクション実行', 'このアイテムをお気に入りに追加しました!');
    console.log('お気に入りアクションが実行されました。');
  };

  const handleShare = () => {
    Alert.alert('アクション実行', 'このアイテムを共有します。');
    console.log('共有アクションが実行されました。');
  };

  return (
    <View style={styles.container}>
      <Text style={styles.header}>基本的なテキストアクション</Text>
      <Text
        style={styles.text}
        // スクリーンリーダーがこのテキストを読み上げる際の代替テキスト
        accessibilityLabel="最新ニュース記事"
        // スクリーンリーダーユーザーが実行できるアクションの定義
        accessibilityActions={[
          { name: 'addToFavorites', label: 'お気に入りに追加' },
          { name: 'shareArticle', label: '記事を共有' },
        ]}
        // アクションが実行されたときに呼び出されるコールバック
        onAccessibilityAction={(event) => {
          // どのカスタムアクションが実行されたかを判別
          switch (event.nativeEvent.actionName) {
            case 'addToFavorites':
              handleFavorite();
              break;
            case 'shareArticle':
              handleShare();
              break;
            default:
              console.warn(`未知のアクション: ${event.nativeEvent.actionName}`);
              break;
          }
        }}
      >
        React Native の最新アップデートに関する記事です。
        このテキストに対してアクセシビリティアクションを試してみてください。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 16,
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    borderRadius: 5,
    textAlign: 'center',
  },
});

export default BasicTextActionsExample;

解説

  • onAccessibilityAction: スクリーンリーダーユーザーが上記で定義したアクションのいずれかを選択したときに呼び出されます。event.nativeEvent.actionName を使用して、どの特定のアクションが実行されたかを識別し、それに応じた処理を実行します。
  • accessibilityActions: オブジェクトの配列を受け取ります。各オブジェクトは以下を持ちます。
    • name: アクションの内部的な識別子(例: addToFavorites)。onAccessibilityAction でこの名前を使ってアクションを判別します。
    • label: スクリーンリーダーがユーザーに表示する、ユーザーフレンドリーな説明(例: お気に入りに追加)。
  • accessibilityLabel: Text コンポーネントが読み上げられる際の代替テキストを提供します。

例2: 複数のテキストコンポーネントと動的なアクション

この例では、リスト内の複数のテキストアイテムそれぞれに、異なるIDに基づいてカスタムアクションを提供する方法を示します。

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

const DATA = [
  { id: '1', title: '記事 A のタイトル', author: '著者 X' },
  { id: '2', title: '記事 B のタイトル', author: '著者 Y' },
  { id: '3', title: '記事 C のタイトル', author: '著者 Z' },
];

const DynamicTextActionsExample = () => {
  const handleReadMore = (itemId, itemTitle) => {
    Alert.alert('詳細を読む', `${itemTitle} の詳細ページに移動します。`);
    console.log(`アイテムID: ${itemId} の詳細を読みます。`);
  };

  const handleBookmark = (itemId, itemTitle) => {
    Alert.alert('ブックマーク', `${itemTitle} をブックマークしました。`);
    console.log(`アイテムID: ${itemId} をブックマークしました。`);
  };

  const renderItem = ({ item }) => (
    <View style={styles.itemContainer}>
      <Text
        style={styles.itemTitle}
        accessibilityLabel={`${item.title}、著者: ${item.author}`}
        accessibilityActions={[
          { name: 'readMore', label: '詳細を読む' },
          { name: 'bookmark', label: 'ブックマークに追加' },
        ]}
        onAccessibilityAction={(event) => {
          switch (event.nativeEvent.actionName) {
            case 'readMore':
              handleReadMore(item.id, item.title);
              break;
            case 'bookmark':
              handleBookmark(item.id, item.title);
              break;
            default:
              console.warn(`未知のアクション: ${event.nativeEvent.actionName}`);
              break;
          }
        }}
      >
        {item.title}
        {'\n'}
        <Text style={styles.itemAuthor}>著者: {item.author}</Text>
      </Text>
    </View>
  );

  return (
    <View style={styles.container}>
      <Text style={styles.header}>動的なテキストアクション</Text>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        style={styles.list}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  list: {
    width: '100%',
  },
  itemContainer: {
    backgroundColor: '#fff',
    padding: 15,
    marginVertical: 8,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#eee',
    elevation: 2, // Android shadow
    shadowColor: '#000', // iOS shadow
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.2,
    shadowRadius: 1.41,
  },
  itemTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  itemAuthor: {
    fontSize: 14,
    color: '#666',
  },
});

export default DynamicTextActionsExample;

解説

  • onAccessibilityAction のコールバック内で、渡された item.iditem.title を利用して、どの記事に対するアクションかを識別し、適切な処理を実行しています。これにより、同じロジックを複数のアイテムに適用できます。
  • renderItem 関数内で、各記事の Text コンポーネントに対して accessibilityActions を設定しています。
  • FlatList を使用して、複数の記事アイテムをレンダリングしています。

例3: アクションの取り消し(トグル)機能

あるテキストが特定の状態(例えば、ミュート/ミュート解除)をトグルするような場合のアクションです。

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

const ToggleTextActionsExample = () => {
  const [isMuted, setIsMuted] = useState(false);

  const toggleMute = () => {
    setIsMuted(!isMuted);
    Alert.alert('ステータス変更', `通知が${isMuted ? '有効' : '無効'}になりました。`);
    console.log(`通知が${isMuted ? '有効' : '無効'}になりました。`);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.header}>トグルアクション</Text>
      <Text
        style={[styles.text, isMuted ? styles.mutedText : null]}
        accessibilityLabel={`通知ステータス: 現在${isMuted ? 'ミュート中' : 'ミュート解除中'}`}
        accessibilityActions={[
          { name: 'toggleMute', label: isMuted ? '通知をミュート解除' : '通知をミュート' },
        ]}
        onAccessibilityAction={(event) => {
          if (event.nativeEvent.actionName === 'toggleMute') {
            toggleMute();
          }
        }}
      >
        現在の通知ステータス: {isMuted ? 'ミュート中' : 'ミュート解除中'}
        {'\n'}
        このテキストに対して通知の状態を切り替えるアクションを試してください。
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 16,
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    borderRadius: 5,
    textAlign: 'center',
  },
  mutedText: {
    backgroundColor: '#ffe0b2', // 薄いオレンジ色
  },
});

export default ToggleTextActionsExample;

解説

  • toggleMute 関数は isMuted の状態を反転させ、それに応じてUIとアクセシビリティラベルが更新されます。
  • accessibilityActionslabel プロパティは、isMuted の状態に基づいて動的に変更されます。これにより、スクリーンリーダーユーザーは現在の状態に応じて適切なアクションが提示されます。
  • useState フックを使用して、isMuted という状態を管理しています。

これらの例を実際に動かすには、実機またはシミュレータで以下の手順でスクリーンリーダーを有効にする必要があります。

  • Android (TalkBack)
    1. 「設定」アプリを開く。
    2. 「ユーザー補助」または「アクセシビリティ」をタップ。
    3. 「TalkBack」をタップし、オンにする。
    4. アプリに戻り、テキストをタップしてフォーカスを合わせ、2本指で左右にスワイプして利用可能なアクションを探索し、目的のアクションをダブルタップして選択します。
  • iOS (VoiceOver)
    1. 「設定」アプリを開く。
    2. 「アクセシビリティ」をタップ。
    3. 「VoiceOver」をタップし、オンにする。
    4. アプリに戻り、テキストにフォーカスを合わせ、2本指で上下にスワイプしてアクションメニューを表示し、目的のアクションを選択します。


View コンポーネントとそのアクセシビリティプロパティを活用する

Text コンポーネントに直接 accessibilityActions を適用する代わりに、TextView でラップし、その View にアクセシビリティプロパティを設定する方法です。これは、複数の子要素を持つUIの領域全体に対してアクセシビリティアクションを提供したい場合に特に役立ちます。

利点

  • onPress との組み合わせ
    ViewTouchableOpacity などの Touchable コンポーネントでラップし、onPress でメインのアクション(例: 詳細画面への遷移)を提供しつつ、accessibilityActions で追加のアクションを提供するといった使い分けが可能です。
  • 論理的なグループ化
    関連する複数の要素を一つの論理的なアクセス可能なユニットとしてグループ化するのに適しています。
  • 柔軟性
    View は任意のコンポーネントをラップできるため、テキストだけでなくアイコンや他のUI要素を含む領域に対してもアクションを提供できます。

考慮事項

  • accessibilityRole を適切に設定することで、スクリーンリーダーにその領域の役割(例: button, link)を伝えることができます。
  • accessibilityLabel は、ラップされた View 内のすべてのコンテンツを要約するよう、適切に記述する必要があります。


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

const AlternativeViewExample = () => {
  const handleMainAction = () => {
    Alert.alert('メインアクション', 'アイテムがタップされました!');
    console.log('メインアクションが実行されました。');
  };

  const handleCustomAction = () => {
    Alert.alert('カスタムアクション', '追加アクションが実行されました!');
    console.log('追加アクションが実行されました。');
  };

  return (
    <View style={styles.container}>
      <Text style={styles.header}>Viewでアクション</Text>
      <TouchableOpacity
        style={styles.itemContainer}
        onPress={handleMainAction} // メインのタップアクション
        accessibilityLabel="ユーザープロフィールカード" // この領域全体のラベル
        accessibilityHint="タップすると詳細が表示されます。アクションメニューを開くには2本指で上下にスワイプしてください。" // アクションのヒント
        accessibilityRole="button" // ボタンとして認識させる
        accessibilityActions={[
          { name: 'editProfile', label: 'プロフィールを編集' },
          { name: 'sendMessage', label: 'メッセージを送る' },
        ]}
        onAccessibilityAction={(event) => {
          switch (event.nativeEvent.actionName) {
            case 'editProfile':
              handleCustomAction();
              break;
            case 'sendMessage':
              Alert.alert('送信', 'メッセージ送信ダイアログが開きます。');
              break;
            default:
              break;
          }
        }}
      >
        <Text style={styles.profileName}>ジョン・ドウ</Text>
        <Text style={styles.profileBio}>モバイルアプリ開発者</Text>
        <Text style={styles.profileEmail}>[email protected]</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  itemContainer: {
    backgroundColor: '#fff',
    padding: 20,
    borderRadius: 10,
    borderWidth: 1,
    borderColor: '#ddd',
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 3,
    elevation: 5,
    width: '90%',
  },
  profileName: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 5,
  },
  profileBio: {
    fontSize: 16,
    color: '#555',
    marginBottom: 5,
  },
  profileEmail: {
    fontSize: 14,
    color: '#888',
  },
});

export default AlternativeViewExample;

コンテキストメニュー / ドロップダウンメニューの利用(UIの代替手段)

これは直接的なアクセシビリティAPIの代替というよりは、アクセシビリティを考慮したUI設計の代替です。スクリーンリーダーユーザーだけでなく、すべてのユーザーに対して、テキストに関連する追加操作を視覚的に提供する方法です。

利点

  • UIの一貫性
    アプリケーション全体のUIパターンとして確立しやすい。
  • 視覚的な提示
    利用可能なアクションが明確に表示されるため、発見しやすい。
  • 普遍的な利用
    スクリーンリーダーの有無にかかわらず、すべてのユーザーが利用できる。

考慮事項

  • 余分なタップ数
    テキストをタップしてメニューを開き、さらにメニュー項目をタップする必要があるため、accessibilityActions に比べて操作ステップが増える可能性があります。
  • スクリーンリーダーでのアクセス性
    実装によっては、これらのメニュー自体がスクリーンリーダーで正しく操作できるかを確認する必要があります(例: メニューが開いたことが読み上げられるか、メニュー項目にフォーカスが当たるか)。


  • 長押し(Long Press)でコンテキストメニューを表示
    TextTouchableOpacityPressable でラップし、onLongPress でカスタムメニューを表示する。
import React, { useState } from 'react';
import { View, Text, Alert, StyleSheet, TouchableOpacity, ActionSheetIOS, Platform } from 'react-native';

const ContextMenuExample = () => {
  const [menuVisible, setMenuVisible] = useState(false);

  const showContextMenu = () => {
    if (Platform.OS === 'ios') {
      ActionSheetIOS.showActionSheetWithOptions(
        {
          options: ['キャンセル', 'コピー', '検索', 'レポート'],
          cancelButtonIndex: 0,
        },
        (buttonIndex) => {
          if (buttonIndex === 1) {
            Alert.alert('アクション', 'テキストをコピーしました!');
          } else if (buttonIndex === 2) {
            Alert.alert('アクション', 'ウェブで検索します。');
          } else if (buttonIndex === 3) {
            Alert.alert('アクション', 'このコンテンツを報告します。');
          }
        }
      );
    } else {
      Alert.alert(
        'メニューオプション',
        '何がしたいですか?',
        [
          { text: 'コピー', onPress: () => Alert.alert('アクション', 'テキストをコピーしました!') },
          { text: '検索', onPress: () => Alert.alert('アクション', 'ウェブで検索します。') },
          { text: 'レポート', onPress: () => Alert.alert('アクション', 'このコンテンツを報告します。') },
          { text: 'キャンセル', style: 'cancel' },
        ]
      );
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.header}>コンテキストメニューの代替</Text>
      <TouchableOpacity
        onLongPress={showContextMenu} // 長押しでメニュー表示
        style={styles.textContainer}
        accessibilityLabel="タップして長押しすると追加オプションが表示されます。"
        accessibilityRole="text" // テキストとして認識させる
      >
        <Text style={styles.text}>
          このテキストを長押しすると、コピーや検索などのオプションが表示されます。
        </Text>
        <Text style={styles.hintText}>(長押ししてください)</Text>
      </TouchableOpacity>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#F5FCFF',
  },
  header: {
    fontSize: 22,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  textContainer: {
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 15,
    borderRadius: 8,
    backgroundColor: '#fff',
    alignItems: 'center',
    width: '90%',
  },
  text: {
    fontSize: 16,
    textAlign: 'center',
  },
  hintText: {
    fontSize: 12,
    color: '#888',
    marginTop: 5,
  },
});

export default ContextMenuExample;

ナビゲーションパターンによる代替

あるテキストや情報が、別の画面で詳細なアクションを伴うものである場合、そのテキストをタップして詳細画面に遷移させ、その詳細画面で必要なアクション(編集、削除など)を提供する方法です。

利点

  • 情報量が多い場合
    テキストに関連するアクションが複数あり、それらをすべて accessibilityActions で提示すると混乱を招く場合に有効です。
  • 画面の明確な分離
    特定のアクションが特定の画面に属するという、論理的なUIフローを確立できます。
  • アクセシビリティの継続性
    画面遷移後も、スクリーンリーダーユーザーが新しい画面のコンテンツとアクションを容易に理解できるかを確認する必要があります。
  • 遷移の手間
    アクションを実行するために、ユーザーが別の画面に遷移する必要があるため、ステップが増えます。
  • テキストがより詳細な情報や複数のアクションに関連しており、専用の画面で管理するのが適切な場合
    ナビゲーションパターンを検討します。
  • アクセシビリティだけでなく、すべてのユーザーに対して視覚的にもアクションを提供したい場合
    コンテキストメニューや隣接するアイコンボタンなどのUI代替手段を検討します。その際も、アクセシビリティのテストは必須です。
  • 複数のUI要素を含むグループ全体に対してアクションを提供したい場合、またはメインのタップアクションに加えて補助的なアクションを提供したい場合
    View をラップし、その ViewaccessibilityActions を設定する方法が適しています。
  • テキスト自体に密接に関連する少数の特定のアクションを提供したい場合
    Text#accessibilityActions が最も直接的で効率的です。