4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コンポーネントごとに考えるアクセシビリティAdvent Calendar 2023

Day 25

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

Last updated at Posted at 2023-12-24

はじめに

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

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

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

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

アクセシビリティを意識したツールチップの仕様

⚪︎ ツールチップとは?

ツールチップは、要素がキーボードフォーカスを受け取る時か、マウスホバーされている時に、その要素に関連する情報を表示するコンポーネントです。

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

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

  • Escapeキー
    • ツールチップを非表示にする

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

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

アクセシビリティを意識したツールチップの完成形

See the Pen Tooltip Accessibillity by でぐぅー | Qiita (@sp_degu) on CodePen.

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

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>

2. 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では、他のコンポーネントにも焦点を当てて、アクセシビリティについても解説しているので、ぜひ購読していてください。


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

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?