グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



Visually Hiddenパターンでアクセシビリティに配慮したマークアップを意識する

React Testing Library や Chrome の開発者ツールでアクセシビリティツリーが見えるようになるなど、最近はただ見た目通りにマークアップするだけではなく、アクセシビリティに配慮したマークアップも必要なスキルセットになってきている流れを感じます。

筆者といえば、最近HTMLやCSSをよく書くようになり、マークアップ完全に理解したからマークアップなんも分からんとなっている状況です。(アクセシビリティも同様)

今回はマークアップなんも分からんといった状況の筆者が、アクセシビリティに配慮したマークアップを意識するといった内容になります。HTML、CSS、アクセシビリティは難しいので鵜呑みにせずに適宜MDNなどのドキュメントをご確認ください。

例えばチェックボックスを装飾したい

無性にチェックボックスを装飾したいとき、あると思います。
例えばこんな感じのチェックボックスですね。

装飾したチェックボックス

inputタグのチェックボックスではできる装飾が限られているので、一定以上の装飾を施したいとなったら、疑似要素や他の要素に施した装飾でチェックボックスの擬似的な見た目を作り、input  [type=”checkbox”]  を隠すことが多いと思います。

この時点での開発者ツールのアクセシビリティツリーはこのようになっています。

開発者ツールのアクセシビリティツリー

※Chrome開発者ツールのアクセシビリティツリーの使用方法は こちら を参照

チェックボックスの装飾も終わったので、 input[type=”checkbox”]  display: none  で隠すとしましょう。

inputを隠したチェックボックス

いい感じですね。今夜は祝杯するしかない。

ここで開発者ツールのアクセシビリティツリーをもう一度確認してみます。

開発者ツールのアクセシビリティツリー

お分かりいただけたでしょうか。
チェックボックスがアクセシビリティツリー上からいなくなっています。

display: none の要素をスクリーンリーダーなどの支援技術は読み上げてくれない

CSSで  display: none  とした要素をスクリーンリーダーは読み上げてくれません。

直訳すると  表示:なし  としてるんだから当然といえばその通り。

視覚的に隠したいけど、スクリーンリーダーには読み上げて欲しい

こういった時に役立つ Visually Hidden というパターンがあります。

Bootstrapや主要なCSSフレームワークでもユーティリティクラスとして提供されているようですね。

色々と流儀はあるようですが概ねこんな感じのCSSです。

.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  width: 1px;
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

VoiceOverが読み上げるにはwidth, heightが1px以上必要で、
また  white-space: nowrap  を指定しないことでスクリーンリーダーがスペースをつなげて不自然な読み上げ(※1)をしてしまうことがあるようです。

※1 参考:Beware smushed off-screen accessible text

display: none を Visually Hidden に置き換える

inputタグに指定していた  display: none  を削除して、 .visually-hidden  クラスをinputタグに指定するようにします。

Visually Hiddenに置き換えたチェックボックス

見た目は  display: none  と変わりありません。

Visually Hiddenに置き換えたアクセシビリティツリー

アクセシビリティツリーにもチェックボックスが表示されるようになりました。

今回のケースではここまで話題に挙げていませんでしたが、 display: none  で隠したinput要素にはフォーカスが当たらなくなります。
筆者はフォームなどではキーボードのタブ移動を使用することが多いのですが、タブ移動ができないフォームはちょっとしたストレスです。

今回のケースでは Visually Hidden パターンを使用することで、タブ移動時にフォーカスが当たるようになる副次的なメリットもあります。

タブ移動時のフォーカス

さいごに

今回実装したチェックボックスのコードを記載しておきます。
※create-viteを使用して作成した Vite + React + TypeScriptを使用

App.tsx

import { FC } from "react";

import './App.css'

export const App: FC = () => {
  return (
    <div className="App">
      <label htmlFor="c-01" className="checkbox-wrapper">
        <input type="checkbox" id="c-01" name="checkbox" className="visually-hidden" />
        <span className="icon" />
        <span className="label">Alice</span>
      </label>
      <label htmlFor="c-02" className="checkbox-wrapper">
        <input type="checkbox" id="c-02" name="checkbox" className="visually-hidden" />
        <span className="icon" />
        <span className="label">Bob</span>
      </label>
      <label htmlFor="c-03" className="checkbox-wrapper">
        <input type="checkbox" id="c-03" name="checkbox" className="visually-hidden" />
        <span className="icon" />
        <span className="label">Charlie</span>
      </label>
    </div>
  )
}

App.css

#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border-width: 0;
}

label {
  cursor: pointer;
}

.App {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.checkbox-wrapper {
  display: inline-flex;
  gap: 15px;
  font-size: 1.4rem;
}

.icon {
  position: relative;
  display: inline-block;
  width: 20px;
  height: 20px;
}

.icon::before,
.icon::after {
  position: absolute;
  content: "";
}

.icon::before {
  width: 100%;
  height: 100%;
  border: 2px solid #99a9c1;
  border-radius: 3px;
}

.icon::after {
  top: 50%;
  left: 60%;
  width: 10px;
  height: 5px;
  margin-right: -50%;
  border-bottom: 3px solid #2b9cce;
  border-left: 3px solid #2b9cce;
  opacity: 0;
  transition: 0.5s;
  transform: translate(-50%, -50%) scale(4);
}

.label {
  position: relative;
}

input:not(focus):focus-visible + .icon::before {
  border: 2px solid #2b9cce;
}

input:not(focus):focus-visible ~ .label::before {
  position: absolute;
  content: "";
  width: 100%;
  height: 100%;
  border-bottom: 2px solid #2b9cce;
}

input:checked + .icon::after {
  opacity: 1;
  transform: translate(-50%, -50%) rotate(-45deg);
}

アクセシビリティに配慮することで、スクリーンリーダーのような支援技術が必要な方だけでなく、タブ移動をする方など全ての方に体験がよい(アクセシブルな)マークアップを日頃から意識していきたいですね。

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします