【アクセシビリティ】アクセシビリティを意識したツールチップの作り方

## はじめに
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390720/efaac723-8d5f-a8fc-5d0d-433ec934b2ea.png)

みなさんアクセシビリティを意識して開発できていますか？

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

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

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

## アクセシビリティを意識したツールチップの仕様
### ⚪︎ ツールチップとは？
ツールチップは、要素がキーボードフォーカスを受け取る時か、マウスホバーされている時に、その要素に関連する情報を表示するコンポーネントです。

また、ツールチップは、少し遅延した後に表示され、Escapeが押されるか、マウスが離れると非表示になります。

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

+ **`Escapeキー`**
  + ツールチップを非表示にする

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

+ ツールチップのコンテナとして機能する要素に `role="tooltip"` を設定する
+ ツールチップを表示させるためのトリガーになる要素には、ツールチップ要素のIDを指定した `aria-describedby` を設定する

## アクセシビリティを意識したツールチップの完成形
<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="PoVaVLd" data-editable="true" data-user="sp_degu" style="height: 300px; 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/PoVaVLd">
  Tooltip Accessibillity</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
<div class="tooltipWrapper">
  <button class="tooltip-target material-symbols-outlined" aria-describedby="align-left">
    format_align_left
  </button>
  <span class="visuallyhidden">左揃え</span>
  <div role="tooltip" id="align-left" class="tooltip">左揃え</div>
</div>

<div class="tooltipWrapper">
  <button class="tooltip-target material-symbols-outlined" aria-describedby="align-left">
    format_align_center
  </button>
  <span class="visuallyhidden">中央揃え</span>
  <div role="tooltip" id="align-left" class="tooltip">中央揃え</div>
</div>

<div class="tooltipWrapper">
  <button class="tooltip-target material-symbols-outlined" aria-describedby="align-left">
    format_align_right
  </button>
  <span class="visuallyhidden">右揃え</span>
  <div role="tooltip" id="align-left" class="tooltip">右揃え</div>
</div>
```

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

.tooltipWrapper {
  position: relative;
}

[role="tooltip"] {
  backdrop-filter: blur(50px);
  background-color: rgb(128 128 128 / .3);
  background-blend-mode: luminosity;
  border-radius: 50px;
  font-size: 14px;
  left: 50%;
  padding: 8px 16px;
  position: absolute;
  top: 48px;
  transform: translateX(-50%);
  visibility: hidden;
  white-space: nowrap;
  &::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: 32px;
    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;
  }
}

button {
  border: none;
  border-radius: 50%;
  background: none;
  color: #ffffff;
  padding: 8px;
  
  &:hover, &:focus, &:active {
    background: radial-gradient(34.12% 136.61% at 50% 100%, rgba(94, 94, 94, 0.14) 0%, rgba(94, 94, 94, 0.00) 73.85%), radial-gradient(50% 164.29% at 50% 100%, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.00) 60.33%), 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;
  }
}

.visuallyhidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(1px, 1px, 1px, 1px);
  -webkit-clip-path: inset(0px 0px 99.9% 99.9%);
  clip-path: inset(0px 0px 99.9% 99.9%);
  border: 0;
}
```

### 3. JavaScriptを実装する
```sample.js
window.addEventListener("DOMContentLoaded", () => {
  const tooltipTargets = document.querySelectorAll('.tooltip-target');
  
  tooltipTargets.forEach(tooltipTarget => {
    tooltipTarget.addEventListener("focus", showTooltip);
    tooltipTarget.addEventListener("blur", hideTooltip);
    tooltipTarget.addEventListener("mouseover", showTooltip);
    tooltipTarget.addEventListener("mouseleave", hideTooltip);
  });
  
  let isShow = false;
  
  window.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && isShow) {
      hideTooltip(e);
    }
  });

  function showTooltip (e) {
    const parent = e.target.parentNode;
    parent.querySelectorAll('[role="tooltip"]').forEach((t) => {
      t.style.visibility = "visible";
    });
    isShow = true;
  };

  function hideTooltip (e) {
    const parent = e.target.parentNode;
    parent.querySelectorAll('[role="tooltip"]').forEach((t) => {
      t.style.visibility = "hidden";
    });
    isShow = false;
  };
});
```

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

ぜひこの記事をストックして、ツールチップを実装する時にアクセシビリティについて思い出してもらえると嬉しいです。

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

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

---

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

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

https://twitter.com/degudegu2510