シリーズ「アクセシビリティ通信」ではウェブアクセシビリティに関するトピックを取り上げていきます。今回は aria-hidden 属性について取り上げます。

aria-hidden は、WAI-ARIA で定義されている属性(state)で、要素がアクセシビリティ API に公開されているかどうかを示すことができます。

ある要素に対して aria-hidden="true" を指定すると、その要素自身と子孫要素はアクセシビリティツリーから除外されます。

これは装飾目的の <img> 要素に alt=""(空の値)を指定することに似ており、スクリーンリーダー等の支援技術に対して、不要または冗長なコンテンツを非表示にするために使用されます。

ただ、<img> 要素とは異なり、aria-hidden="true" は指定した要素の子孫要素にも影響が及ぶ(隠してしまう)ので注意が必要です。

aria-hidden を指定する例
<div aria-hidden>Foo</div>
<div aria-hidden="false">Bar</div>
<div aria-hidden="true">Baz</div>

このコードを Google Chrome のデベロッパーツールで確認すると、aria-hidden="true" を指定した要素(Baz)がアクセシビリティツリーから除外されていることがわかります。

Google Chrome のデベロッパーツールのスクリーンショット。アクセシビリティツリーに「Foo」「Bar」が含まれているが、`aria-hidden="true"` を指定した「Baz」は含まれていない

aria-hidden="true" とフォーカス可能な要素

この見出しのリンク

aria-hidden="true" を指定した要素自身がフォーカス可能であったり、その子孫要素にフォーカス可能な要素を含めてはいけません。これは aria-hidden="true" を指定しても、キーボードなどでフォーカスできてしまい混乱を招くためです。

フォーカス可能な要素としては、<a><button><input><select><textarea><summary> などの要素、tabindex="0"contenteditable 属性を指定した要素などが挙げられます。

この問題を解消するには、フォーカス可能な要素に tabindex="-1" を指定したり、後述する inert 属性で不活性化するといった方法が考えられます。

フォーカス可能な要素に tabindex="-1" を指定する例
<p aria-hidden="true">
  <a href="#" tabindex="-1">Foo</a>
  <button type="button" tabindex="-1">Bar</button>
</p>

aria-hidden="true" と同様に、アクセシビリティツリーから要素を除外する方法としては以下が挙げられますが、微妙に使用する文脈が異なります。

  • CSS の display: none および visibility: hidden
  • HTML の hidden 属性
  • HTML の inert 属性

順番に確認していきましょう。

display: none および visibility: hidden との比較

この見出しのリンク

CSS の display: none および visibility: hidden では視覚的にも非表示になりますが、aria-hidden="true" では視覚的には表示されたままになります。

このことから aria-hidden="true" は、装飾要素として視覚的には表示したままで、アクセシビリティツリーからは取り除きたいときに使用することがわかります。

hidden 属性との比較

この見出しのリンク

HTML の hidden 属性では、通常はユーザエージェント(ブラウザ)の display: none が適用されるので、先ほどと同じく視覚的にも非表示になります。

ちなみに、このユーザエージェントが持つ hidden 属性 の display: none の指定は、ユーザや開発者の CSS によって簡単に上書きすることができ、例えば [hidden] { display: block; } と指定すれば、可視化されアクセシビリティツリーにも表示されます。

これらを避けるためには、リセット CSS で上書きされないように明示します。

リセット CSS で [hidden] のスタイルを明示する例
[hidden]:not([hidden="until-found"]) {
  display: none !important;
}
  • hidden 属性には、アンカーリンクやブラウザのページ内検索で遷移したときに表示される until-found というキーワードを指定できるので1、上記のように :not() 擬似クラスを指定しています。
  • !important ではなく、@layer を使用して優先順位を指定したほうがスマートかもしれません。

inert 属性との比較

この見出しのリンク

HTML の inert 属性では、視覚的には表示され、子孫要素も含めてアクセシビリティツリーから取り除かれる点は aria-hidden="true" と同じですが、不活性化される点が異なります。

つまり、属性を指定した子孫要素にフォーカス可能な要素(<a><button>)を含んでいる場合、aria-hidden="true" の場合は依然としてキーボード操作でフォーカスすることができますが、inert ではこれらも含めて無効になります。

ただ、inert を指定した場合、テキスト選択やコンテキストメニュー、クリックイベントなど、すべての操作ができなくなるので注意が必要です。

子孫要素にフォーカス可能な要素を含む例
<!-- aria-hidden="true" -->
<p aria-hidden="true">
  <a href="#" tabindex="-1">Foo</a>
  <button type="button" tabindex="-1">Bar</button>
</p>

<!-- inert -->
<p inert>
  <a href="#">Foo</a>
  <button type="button">Bar</button>
</p>

上記のコードでは、aria-hidden="true" の子孫要素へのキーボードフォーカスを禁止するために、tabindex="-1" を指定しています。


続いて、よくある例を取り上げながら aria-hidden 属性の使い方を見ていきます。

まず、もっともよく見られるケースとしては、装飾目的のインライン SVG の要素に対して aria-hidden="true" を指定する例が挙げられます。

インライン SVG の例
<svg viewBox="0 0 40 20" aria-hidden="true">
  <circle r="10" cx="10" cy="10" fill="#007373" />
  <rect x="20" y="0" width="20" height="20" fill="#bde1e1" />
</svg>

インライン SVG は、拡大縮小しても劣化せず、単純な図形であればパフォーマンスも高いため、ウェブサイトやウェブアプリで使用するケースは増えていますが、装飾目的の場合には aria-hidden="true" を指定して、アクセシビリティツリーから取り除くのがよいでしょう。

一方で、情報として意味のある画像には aria-hidden="true" を指定すべきではありません。<title> 要素や aria-label 属性を指定して、支援技術にも情報が伝わるようにしましょう。

SVG に <title> を指定する例
<svg viewBox="0 0 40 40" role="img">
  <title>検索</title>
  <!-- ... -->
</svg>
SVG に aria-label を指定する例
<svg viewBox="0 0 40 40" role="img" aria-label="検索">
  <!-- ... -->
</svg>

なお、SVG コードの最適化については、以下の記事でも説明しています。

次に、ここ最近のウェブデザインの表現としてよく見られるティッカー(ニュースティッカー、スライダー)について考えてみます。

水平方向にスクロールするテキストベースのティッカー(イメージ)

テキストや画像がスクロールして流れますが、幅全体を埋める必要があるので、同じ要素を繰り返し配置するケースがあります。以下はコードの例です。

ティッカーの例
<!-- HTML -->
<text-ticker>
  <div class="c-track">
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
  </div>
</text-ticker>

<!-- CSS -->
<style>
text-ticker {
  overflow: hidden;
}
text-ticker .c-track {
  display: flex;
  inline-size: min-content;
  font-family: sans-serif;
  font-size: 4cqi;
  text-transform: uppercase;
  white-space: nowrap;
  animation: ticker 40s linear infinite;
}
text-ticker .c-track > * {
  margin-inline-end: 1em;
}
@keyframes ticker {
  from {
    translate: 0 0;
  }
  to {
    translate: -50% 0;
  }
}
</style>

このとき「No ARIA is better than Bad ARIA」のテキスト2は 4 回読み上げられます。1 回だけ読み上げてほしい場合には、残りの要素に aria-hidden="true" を指定します。

読み上げが不要な要素に対して aria-hidden="true" を指定する例
<text-ticker>
  <div class="c-track">
    <p>No ARIA is better than Bad ARIA</p>
    <p aria-hidden="true">No ARIA is better than Bad ARIA</p>
    <p aria-hidden="true">No ARIA is better than Bad ARIA</p>
    <p aria-hidden="true">No ARIA is better than Bad ARIA</p>
  </div>
</text-ticker>

要素全体が装飾的な役割しかなく、コンテンツとして読み上げる必要がないときには、親要素に aria-hidden="true" を指定します。

要素全体に対して aria-hidden="true" を指定する例
<text-ticker aria-hidden="true">
  <div class="c-track">
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
    <p>No ARIA is better than Bad ARIA</p>
  </div>
</text-ticker>

また、「aria-hidden="true" とフォーカス可能な要素」でも述べたように、aria-hidden="true" を指定した要素にフォーカス可能な要素が含まれる場合には、tabindex="-1" を指定してフォーカスできないように対応しましょう。

WCAG「2.2.2 一時停止、停止、非表示」

見出し「WCAG「2.2.2 一時停止、停止、非表示」」

WCAG の「2.2.2 一時停止、停止、非表示(レベル A)」では、以下の達成基準が定められています。

動き、点滅、スクロール

動きのある、点滅している、又はスクロールしている情報が、(1) 自動的に開始し、(2) 5 秒よりも長く継続し、かつ、(3) その他のコンテンツと並行して提示される場合、利用者がそれらを一時停止、停止、又は非表示にすることのできるメカニズムがある。ただし、その動き、点滅、又はスクロールが必要不可欠な動作の一部である場合は除く。

達成基準 2.2.2 一時停止、停止、非表示 | WCAG 2.1 解説書

加えて、このページでは以下の説明が記載されています。

利用者がフォーカスしている時だけアニメーションが停止する(そしてフォーカスを解除し次第再開する)ようなものは、その過程でページが使い物にならなくするため「利用者が一時停止するためのメカニズム」とは判断されず、この達成基準も満たさないだろう。

このように、フォーカス時の一時停止のみではこの基準を満たすことはできないため、一時停止・再生のトグルボタンを用意するといった対応が望ましいでしょう。

CSS の prefers-reduced-motion を使用する方法3も考えられますが、設定方法を知らないユーザや、変更が OS 全体に反映されることを望まないケースもあることを踏まえると、一時停止・再開できる機能もあわせて用意するのがより理想的ではあります(開発コストは増えますが)。もちろん、アニメーション自体の必要性についても考える必要があるでしょう。

例3: 文字ごとに区切ったテキスト

見出し「例3: 文字ごとに区切ったテキスト」

最後に文字ごとに <span> 要素などで区切ったテキストの例を見ていきます。

例えば、一文字ずつ順番にアニメーションさせたい場合や、文字ごとに罫線で囲ったスタイルを適用したいケースなどが該当します。

文字ごとに区切ったテキストの例
<p class="hero-ja">
  <span>ウ</span>
  <span>ェ</span>
  <span>ブ</span>
  <span>の</span>
  <span>本</span>
  <span>質</span>
  <span>か</span>
  <span>ら</span>
  <span>考</span>
  <span>え</span>
  <span>る</span>
</p>

<style>
.hero-ja {
  display: flex;
}
.hero-ja > span {
  margin-inline-end: -1px;
  border: solid 1px;
}
</style>

この HTML を macOS の VoiceOver で読み上げると一文字ずつ分割されてしまい、「考える」は「こうえる」と読まれてしまうことがあります。

使用する支援技術とブラウザの組み合わせ、指定されたスタイルによって読み上げの結果は異なります。

macOS VoiceOver のスクリーンショット。通常時と文字ごとに <span> で区切った場合の比較
span で区切ると、一文字ずつ分割されて読み上げられる

この問題を解消するには、<span> 要素で区切ったテキストは aria-hidden="true" を指定してアクセシビリティツリーから除外して、別途読み上げ用のテキストを用意します。

<p class="hero-ja" aria-hidden="true">
  <span>ウ</span>
  <span>ェ</span>
  <span>ブ</span>
  <span>の</span>
  <span>本</span>
  <span>質</span>
  <span>か</span>
  <span>ら</span>
  <span>考</span>
  <span>え</span>
  <span>る</span>
</p>
<p class="sr-only">
  ウェブの本質から考える
</p>

<style>
/* ... */
.sr-only {
  position: absolute;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border-width: 0;
  white-space: nowrap;
}
</style>

また、一文字ずつ <span> 要素で囲うと、Google 翻訳などの拡張機能を使用したときに意図しない翻訳になることがあるので、適宜 translate="no" を指定するのがよいでしょう。

正しく翻訳されたテキストを提供したい場合には、日英の表記を並べて表示する方法や、属性セレクタ([lang="ja"] など)を使用して表示を出し分ける方法が考えられますが、その場合には、デザインレベルでの再考が必要になるかもしれません。

ここまで、3 つの例を取り上げながら aria-hidden="true" の使い方について説明しました。そのほかの例としては、スライダー(カルーセル)におけるビューポート外のスライドに対する指定や、アイコンフォントへの指定が考えられそうですます。

あらゆる状況を踏まえて、完璧にアクセシビリティ対応することはほぼ不可能ですが、ウェブアクセシビリティのチェックツールを使用したり、実際にスクリーンリーダーやキーボードのみで操作してみると、アクセシビリティ上の問題に気づくことができます。

これらをひとつずつ改善して積み上げて、より使いやすくアクセシブルなウェブサイトになることを目指していきましょう。

本記事の作成にあたり、以下のウェブページを参考にしました。

脚注

  1. hidden | MDN

  2. No ARIA is better than Bad ARIA」は、「誤った ARIA を使用するぐらいなら ARIA を指定しないほうが良い」という教訓です。用法・用量を守って正しく使いましょう(自戒の念を込めて)。

  3. C39: モーションの防止に CSS reduce-motion クエリを使用する | WCAG 2.1 達成方法集