Reactのテキストは記述の仕方で読み上げが変わってしまう件について
-
- #React
- #VoiceOver
- #ユーザビリティ
Reactで何かがクリックされた回数を変数count
にいれて画面に表示させるような場合、次のように書かれることがほとんどでしょう[1]。
<p>Clicked {count} times!</p>
筆者がこのようなコードを書く場合はテンプレートリテラルを使うか、変数に一度格納してから出力するようにしています。
<p>{`Clicked ${count} times!`}</p>;
const content = `Clicked ${count} times!`;
// ...
<p>{content}</p>
React 19時点では、JSX内に書かれた文字列はデータの前後で別々のテキストノードが作成されます。1つの文字列として結合しておかないと、テキストが個別のノードに分かれてしまうためです。
コード | ノード数 | テキストノード |
---|---|---|
| 3 | 「Clicked 」「1」「 times!」 |
| 1 | 「Clicked 1 times!」 |
テキストノードが分割されると何が起きるのか
どちらの方法で実装しても表示上の見た目には変化はありませんが、スクリーンリーダーユーザの体験に差を生み出します[2]。
テスト環境 | 結合なしの読み上げ方 | 結合ありの読み上げ方 |
---|---|---|
Windows 11 + NVDA 2025.1.2jp + Firefox 140 | 全文 | 全文 |
macOS 15 + VoiceOver | 全文 | 全文 |
iOS 18 + VoiceOver | 分割 | 全文 |
iPadOS 18 + VoiceOver | 分割 | 全文 |
iOSのVoiceOverはテキストノードごとに読み上げるため、ユーザはテキストノードの数だけ余計にカーソルを動かす必要があります[3]。 読み上げの最適化に取り組んでいるiOSのVoiceOverのユーザが多いWebサイトならば、ユーザビリティの観点から結合してレンダリングする方法を採用するのも1つの選択肢かもしれません。
とくに見出しなどの一息で読み上げて欲しいコンテンツに関しては、一考の余地があるのではないでしょうか。
ただし、ライブリージョンの観点ではノードが分割されていない場合、aria-atomic="true"
のように全文が読み上げられる点には留意が必要です。
どのような読み上げがされてほしいかによって、ライブリージョンの範囲やDOMの構造は変わってくるでしょう。変更部分だけがアナウンスされてほしいようなケースでは、テキストノードが分割された状態のほうがユーザビリティは高いかもしれません。
あくまで豆知識
たしかにVoiceOverのことだけを考えるならば1つの文字列に結合してからテキストは出力した方がいいシーンがあるかもしれません。 一方で、特定の環境のために変わった書き方に統一するというのも少しイマイチに感じます。
筆者としては、iOSおよびiPadOSのVoiceOverが、テキストノード単位ではなく要素単位で読み上げるようにアップデートされることを期待しながら、こういう違いがあるということを頭の片隅に置いておく程度がちょうどよいのかもしれないと思っています。