【アクセシビリティ】アクセシビリティを意識したメッセージダイアログの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/248cf960-aea2-1739-01c9-ea047d3fb63e.png)
みなさんアクセシビリティを意識して開発できていますか？

必要なところに`role属性`を記述したり、`tabキー`でフォーカスができるようにしたりなど、意識しないといけないことも多いです。
そのため、アクセシビリティを完璧にやろうとするのは一苦労です。

ただ、コンポーネントごとに区切って、アクセシビリティを理解しておけば、実装するタイミングに思い出しやすく、アクセシビリティも意識しやすいと思います。

そのため、この記事では「**メッセージダイアログ**」に焦点を当てて、アクセシビリティを意識したメッセージダイアログの実装方法とメッセージダイアログで意識した方がいいアクセシビリティを解説しようと思います。

## アクセシビリティを意識したメッセージダイアログの仕様
### ⚪︎ メッセージダイアログとは？
メッセージダイアログは、ユーザーの作業を中断して重要なメッセージを伝え、応答を取得するダイアロルモーダルです。

メッセージダイアログは以下の点に注意する必要があります。

+ **`role="alertdialog"`を設定すること**
  + ブラウザや支援技術が他のダイアログモーダルと区別して、システム警告音の再生などをできるようにするため
+ **ダイアログモーダルが表示されたら、ダイアログモーダル内にフォーカスが移動すること**
  + 基本的には、ダイアログモーダル内の最初のフォーカス可能な要素にフォーカスを当てる
  + ダイアログモーダルの内容を容易に理解できるような要素（表、リスト、etc..）がある場合、フォーカス可能な要素に`tabindex="-1"`を指定して、その要素にフォーカスを当てることを推奨する
    + この場合、`aria-describedby`を指定しないのが望ましい
  + ダイアログモーダルの内容が多く、最初のフォーカス可能な要素にフォーカスを当てるとコンテンツの最初が見えなくなる場合、フォーカス可能な要素に`tabindex="-1"`を指定して、コンテンツの最初要素にフォーカスを当てることをを水行する
  + ダイアログモーダルに不可逆なアクションが含まれている場合、可逆なアクションにフォーカスを当てるのが望ましい
  + ダイアログモーダルが`追加情報の提供` or `処理の続行`の場合、最も使われそうなアクションにフォーカスを当てるのが望ましい
+ **ダイアログモーダルを非表示したら、ダイアログモーダルを呼び出した要素にフォーカスが戻ること**
  + ダイアログモーダルを呼び出した要素が存在しない場合、次のワークフローに適切な要素にフォーカスする
  + 以下の場合は、次のワークフローに適切な要素にフォーカスする
    + ユーザーがすぐにダイアログモーダルを再度表示させる必要がない場合
    + ダイアログモーダルで完了したタスクが、次のワークフローに直接関連している場合
+ **ダイアログモーダルには、ダイアログモーダルを閉じる役割を持つボタン作ること**
  + `閉じるアイコンボタン` や `キャンセルボタン` などの見える要素で作ること


### ⚪︎ キーボードインタラクション

+ **`Tabキー`**
  + ダイアログモーダル内の次のフォーカス可能な要素にフォーカスを移動する
  + ダイアログモーダル内の最後のフォーカス可能な要素であった場合、最初のフォーカス可能な要素に移動する
+ **`Shiftキー + Tabキー`**
  + ダイアログモーダル内の前のフォーカス可能な要素にフォーカスを移動する
  + ダイアログモーダル内の最初のフォーカス可能な要素であった場合、最後のフォーカス可能な要素に移動する
+ **`Escapeキー`**
  + ダイアログモーダルを閉じる

### ⚪︎ WAI-ARIA の役割、状態、プロパティ

+ メッセージダイアログの要素には、`role="alertdialog"`を設定する
+ メッセージダイアログに目に見えるタイトルがある場合、タイトル要素を参照する`aria-labelledby`を設定する
+ メッセージダイアログに目に見えるタイトルがない場合、`aria-label`を設定する
+ `role="alertdialog"`を設定した要素は、アラートメッセージを含む要素を参照する`aria-describedby`を設定する

## アクセシビリティを意識したメッセージダイアログの完成形
<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="oNJRZvp" data-editable="true" data-user="sp_degu" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/sp_degu/pen/oNJRZvp">
  Alert and Message Dialogs Accessibility</a> by でぐぅー | Qiita (<a href="https://codepen.io/sp_degu">@sp_degu</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## アクセシビリティを意識したメッセージダイアログの作り方
### 1. HTMLを実装する
```sample.html
<button type="button" id="dialog-trigger" popovertarget="dialog">Dialogを表示する</button>

<dialog popover id="dialog" role="alertdialog" aria-labelledby="dialog-title" aria-describedby="dialog_desc">
  <img alt="" width="52" height="52" src="https://drive.google.com/uc?export=view&id=1OJIikCT7Pit4SPz6WsF97KTa4sRU27y2"/>
  <h3 id="dialog-title">記事を削除する</h3>
  <p id="dialog_desc">削除した記事は復旧できません。<br/>本当に削除しますか？</p>
  <hr/>
  <button popovertarget="dialog" popovertargetaction="hidden" class="dialog-button">削除する</button>
  <button popovertarget="dialog" popovertargetaction="hidden" class="dialog-button">キャンセル</button>
</dialog>
```

### ２. CSSを実装する
```sample.css
body {
  background-color: #212529;
  color: #fff;
  display: grid;
  gap: 24px;
  height: calc(100vh - 40px);
  margin: 0;
  padding: 20px 0;
  place-items: center;
  width: 100vw;
}

button {
  align-items: center;
  background: none;
  border: none;
  color: #ffffff;
  cursor: pointer;
  display: flex;
}

#dialog-trigger {
  background: linear-gradient(0deg, rgba(94, 94, 94, 0.18) 0%, rgba(94, 94, 94, 0.18) 100%), rgba(255, 255, 255, 0.06);
  background-blend-mode: color-dodge, lighten;
  border-radius: 100px;
  font-size: 16px;
  font-weight: bold;
  justify-content: center;
  width: 200px;
  padding: 8px 16px;
  position: relative;
}

#dialog-trigger::before {
  background: linear-gradient(135deg, rgb(255 255 255 / .4) 0, rgb(255 255 255 / 0) 40%, rgb(255 255 255 / 0) 60%, rgb(255 255 255 / .1) 100%);
  border: 1.4px solid transparent;
  border-radius: 24px;
  content: "";
  inset: 0;
  position: absolute;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  -webkit-mask-composite: destination-out;
  mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  mask-composite: exclude;
  z-index: -1;
}

#dialog-trigger:hover {
  background: radial-gradient(101.08% 100% at 50% 100%, rgba(94, 94, 94, 0.32) 0%, rgba(94, 94, 94, 0.00) 73.85%), radial-gradient(100.02% 100% at 50% 100%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.00) 68.75%), linear-gradient(0deg, rgba(94, 94, 94, 0.18) 0%, rgba(94, 94, 94, 0.18) 100%), rgba(255, 255, 255, 0.06);
  background-blend-mode: color-dodge, normal, color-dodge, lighten;
  box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.10);
}


#dialog {
  background-color: rgb(128 128 128 / .3);
  border: none;
  border-radius: 24px;
  color: #ffffff;
  gap: 8px;
  padding: 20px;
  position: relative;
  text-align: center;
  background-blend-mode: luminosity;
  backdrop-filter: blur(15px);
  width: 320px;
}

#dialog::before {
  background: linear-gradient(135deg, rgb(255 255 255 / .4) 0, rgb(255 255 255 / 0) 40%, rgb(255 255 255 / 0) 60%, rgb(255 255 255 / .1) 100%);
  border: 1.4px solid transparent;
  border-radius: 20px;
  content: "";
  inset: 0;
  position: absolute;
  -webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  -webkit-mask-composite: destination-out;
  mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0) border-box;
  mask-composite: exclude;
  z-index: -1;
}

h3 {
  font-size: 20px;
  margin: 16px 0 0;
}

p {
  color: rgba(255, 255, 255, 0.60);
  margin: 4px 0 0;
}

hr {
  border: 1px solid;
  border-color: rgb(255 255 255 / .07);
  margin: 16px 0 12px;
}

.dialog-button {
  justify-content: center;
  background: none;
  border-radius: 100px;
  font-weight: 600;
  padding: 12px 16px;
  width: calc(100% - 32px);
  margin: 8px 16px 0;
}

.dialog-button:hover {
  background: radial-gradient(101.08% 100% at 50% 100%, rgba(94, 94, 94, 0.32) 0%, rgba(94, 94, 94, 0.00) 73.85%), radial-gradient(100.02% 100% at 50% 100%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.00) 68.75%), linear-gradient(0deg, rgba(94, 94, 94, 0.18) 0%, rgba(94, 94, 94, 0.18) 100%), rgba(255, 255, 255, 0.06);
  background-blend-mode: color-dodge, normal, color-dodge, lighten;
}
```

## まとめ
この記事では、「**メッセージダイアログ**」に焦点を当てて、アクセシビリティを意識したメッセージダイアログの実装方法とメッセージダイアログで意識した方がいいアクセシビリティを解説しました。

ぜひこの記事をストックして、メッセージダイアログを実装する時にアクセシビリティについて思い出してもらえると嬉しいです。

Advent Calendar 2023では、他のコンポーネントにも焦点を当てて、アクセシビリティについても解説しているので、ぜひ購読していてください。

https://qiita.com/advent-calendar/2023/degudegu2510

---

最後まで読んでくださってありがとうございます！

普段はデザインやフロントエンドを中心にQiitaに記事を投稿しているので、ぜひQiitaのフォローとX(Twitter)のフォローをお願いします。

https://twitter.com/degudegu2510