自社コーポレートサイトのアクセシビリティ向上に取り組み始めた話
こんにちは。イチマルイチデザイン株式会社でフロントエンドエンジニアをしている長谷川です。
この度、自社コーポレートサイト(https://101de-sign.com/)のアクセシビリティ向上の取り組みを始めました。
今回の記事では、アクセシビリティ向上に取り組み始めた経緯や取り組み内容を、備忘録として書いています。
アクセシビリティ周りのちょっとした解説も交えていますが、がっつり解説はしていません。後述していますが、アクセシビリティ向上を実際のプロダクトで行うのは初めてで、この記事は振り返り目的が強めです。
間違いなどございましたらご指摘いただけると幸いです。
※ 自社コーポレートサイトはNext.jsを用いて開発されています。そのためコードはjsx記法を用いて解説しています。
また、スクリーンリーダーはMacのVoiceOverを利用してチェックしています。
なぜ取り組み始めたか
そろそろアクセシビリティ向上をやっていきたいなーと思いつつも、知見が豊富にあるわけではなく、取り組み方も分からない状態でした。
この状態で実際のプロダクトにアクセシビリティ向上の取り組みを行なっても、破綻するだろうなと感じました。
そこで比較的対応がしやすい、自社コーポレートサイトのアクセシビリティ向上に取り組むことにしました。(もちろん許可を得ています。)
まずは自社コーポレートサイトのアクセシビリティ向上に取り組み、そこから最善の取り組み方を探り、得た知見をプロダクトで活用したい目的で始めました。
課題を見つけた
最初にアクセシビリティの課題を見つけることから始めました。
課題の見つけ方としては、Chrome拡張機能の axe DevTools を利用したり、eslint-plugin-jsx-a11y や markuplint を導入して問題のある箇所をエラーとして出力させました。
機械のチェックで出力されたエラーのみではなく、自分の目で見てこうした方が良いと判断した箇所もリスト化しました。
方針を決めた
WCAG(Web Content Accessibility Guidelines)のシングルAに準拠する、キーボード操作で全ての情報にアクセス可能にする、といった具合で、どのような方針でアクセシビリティ向上に取り組むかを考えました。
最初はシングルAに準拠する方針で取り組もうと思ったのですが、デザイン変更を余儀なくされる問題があった場合はデザイナーとの相談になり、大掛かりな取り組みになってしまいます。
また、シングルAに準拠しましたと伝えても、アクセシビリティの知識が無い方には上手く伝わりません。
そこで、あるページ、あるコンポーネントがこの操作でアクセス出来るようになりました、と伝えられたら分かりやすいのではと考えました。
いきなり全ての問題を解決しながら全てのページのアクセシビリティ向上に取り組まず、アクセシビリティが向上したと誰にでも分かりやすい形を目指すため、コンポーネント単位、ページ単位といった粒度でアクセシビリティ向上させる方針に決めました。
向上させるコンポーネントを決めた
今回は、サイト全体に影響があり、共通コンポーネントとなっているグローバルナビゲーションのアクセシビリティ向上を目指すことにしました。
また後述しますが、サイト全体に影響のあるアクセシビリティの問題があれば、それも修正することにしました。
アクセシビリティ向上させる箇所が決まったので、実際にどのような問題があり、どのように向上させたかについてこれから書いていきます。
ページ遷移時のフォーカス位置を管理する
冒頭でも書きましたが、コーポレートサイトはNext.jsで作られています。
SPAで作られたサイトは疑似的なページ遷移となるため、MPAとは違ってページ遷移をしてもフォーカス位置がリセットされません。
この辺りの解説は以下の記事が参考になると思います。
というわけで、ページ遷移時のフォーカス位置をページ先頭(bodyタグ)にするために、_app.tsxに以下の処理を追加しました。
const handleRouteChange = useCallback(() => {
document.body.focus();
}, []);
useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange);
return () => router.events.off('routeChangeComplete', handleRouteChange);
}, [handleRouteChange, router.events]);
この対応によりページ遷移時に、フォーカス位置がページ先頭から始まるようになりました。🎉
SPAだと軽視されがちな問題なので、対応出来て良かったです。
グローバルナビゲーションのアクセシビリティ向上
① aria-current=”page”の付与
現在滞在しているページのナビリンクにaria-current=”page”を付与するようにしました。
const router = useRouter();
return (
<nav>
<ul css={styles.list}>
<li>
<Link
href={PATHS.ci}
aria-current={router.pathname === PATHS.ci ? 'page' : undefined}
>
CI
</Link>
</li>
<li>
<Link
href={PATHS.service}
aria-current={router.pathname === PATHS.service ? 'page' : undefined}
>
Service
</Link>
</li>
<li>
<Link
href={PATHS.works}
aria-current={router.pathname === PATHS.works ? 'page' : undefined}
>
Works
</Link>
</li>
<li>
<Link
href={PATHS.company}
aria-current={router.pathname === PATHS.company ? 'page' : undefined}
>
Company
</Link>
</li>
<li>
<Link
href={PATHS.news}
aria-current={router.pathname === PATHS.recruit ? 'page' : undefined}
>
News
</Link>
</li>
<li>
<Link
href={PATHS.recruit}
aria-current={router.pathname === PATHS.recruit ? 'page' : undefined}
>
Recruit
</Link>
</li>
<li>
<Link href={PATHS.blog} target='_blank' rel='noopener noreferrer'>
Blog
<IconExternalLink />
</Link>
</li>
<li>
<Link
href={PATHS.contact}
aria-current={router.pathname === PATHS.contact ? 'page' : undefined}
>
Contact
</Link>
</li>
</ul>
</nav>
);
この対応で滞在しているページのナビリンクにフォーカスされると、スクリーンリーダーが「現在のページ」と読み上げてくれるようになり、現在どこのページにいるかが認識しやすくなりました。🎉
② 外部リンクをスクリーンリーダーでも分かるようにした
BLOGナビリンクは外部リンクに設定されています。
目が見える人の場合はアイコンを見て、外部のサイトに遷移するリンクだなと認識できると思います。
しかし、スクリーンリーダーを使用しているユーザーにはアイコンがあっても認識できません。
いきなり新しいタブが開かれ、全く別のサイトに遷移させる格好になってしまい、ユーザーを混乱させてしまうかもしれません。
そこで、予め外部リンクと分かるようにスクリーンリーダー用のテキストを追加しました。
<a href='https://note.com/101design/' target='_blank' rel='noopener noreferrer'>
Blog
<span className='sr-only'>新規タブまたはウィンドウで開く</span>
<svg role='img' aria-hidden='true'> ・・・ </svg>
</a>
この変更により、スクリーンリーダーで「新規タブまたはウィンドウで開く」と読み上げてくれるようになり、予め外部リンクと認識させることが可能になりました。🎉
もちろんこれはナビリンクに限った話では無いので、外部リンクにはスクリーンリーダー用のテキストを追加するのが良いと思います。
③ トグルボタンのアクセシビリティ向上
スマホの画面サイズ時に表示される、いわゆるハンバーガーメニューです。
最初は以下のように実装されていました。
<button type='button' onClick={handleClick} ref={ref}>
<svg role='img' aria-hidden='true'> ・・・ </svg>
</button>
一見このマークアップで問題ないように思えますが、アクセシビリティ向上出来るポイントがいくつかあります。
まずこのボタンには、Nameプロパティがありません。フォーカスされてもスクリーンリーダーでは「ボタン」としか読み上げられず、どういったボタンなのか押してみないと分からない状態でした。
上記の解決策として、Nameプロパティを追加する必要があります。
以下のように、buttonタグ内にスクリーンリーダー用のテキストを追加しました。
<button type='button' onClick={handleClick} ref={ref}>
<span className='sr-only'>メニュー</span> // Nameプロパティの追加
<svg role='img' aria-hidden='true'> ・・・ </svg>
</button>
これでどんなボタンなのかが、スクリーンリーダーを使用しているユーザーに認識できるようになりました。🎉
次に、このボタンを押してもスクリーンリーダーユーザーは何が起きたのかが分からない状態です。と言うのは、このままだとボタンを押しても、何も読み上げてくれないからです。
上記の解決策として、開閉状態を認識できるように、 aria-expanded属性とaria-controls属性の追加を行いました。
<button type='button' onClick={handleClick} aria-controls='menu' aria-expanded={isOpen} ref={ref}> // aria-expanded属性とaria-controls属性の追加
<span className='sr-only'>メニュー</span> // Nameプロパティの追加
<svg role='img' aria-hidden='true'> ・・・ </svg>
</button>
<nav id="menu"> // aria-controls属性と紐づくidの追加
・・・
</nav>
上記により、メニューの開閉状態を読み上げてくれるようになりました。🎉
ちなみに開閉状態は、aria指定とNameプロパティの2つを同時に実装する必要はないとのことです。以下がその例です。
<button type='button' onClick={handleClick} aria-controls='menu' aria-expanded={isOpen} ref={ref}>
<span className='sr-only'>{`メニューを${isOpen ? '閉じる' : '開く'}`}</span>
<svg role='img' aria-hidden='true'> ・・・ </svg>
</button>
一見問題ないように思えますが、ARIA指定した開閉状態とNameプロパティの開閉状態、2つの情報が読み上げられます。ユーザーにとって、これは逆に理解するのが難しくなるみたいです。
(参考:Webアプリケーションアクセシビリティ──今日から始める現場からの改善より)
デザインに応じて、ARIA属性かNameプロパティのどちらかで開閉状態を実装するのが望ましいと思います。
④ トグルメニューのアクセシビリティ向上
トグルボタンを押すと画面右からメニューが展開されます。
上記メニューのマークアップは特に問題ありませんでした。
ですが、キーボードで操作してみるとフォーカスがメニュー外に移動してしまうことがわかりました。
これは良くない体験だったので、フォーカスがメニュー外に移動してしまわないように、フォーカストラップと呼ばれるテクニックを用いて制御するようにしました。
{isOpen && (
<div tabIndex={0} onFocus={() => refToggleButton.current?.focus()} />
)}
<ToggleMenuButton
isOpen={isOpen}
handleClick={handleClick}
ref={refToggleButton}
/>
<nav id={'menu'}>
・・・
<div tabIndex={0} onFocus={() => refToggleButton.current?.focus()} />
</nav>
この実装によりフォーカスがメニュー外へ逃げることなく、メニュー内の操作に集中できるようになりました。🎉
ですが、スクリーンリーダーではメニュー外の操作が出来てしまいます。
解決策として、メニュー以外の全ての要素をアクセシビリティツリーから削除するようにしましょう。
・・・とは言っても、メニュー以外の全ての要素にaria-hidden属性を付けることになるのですが、少し大変です。
要素が増える度にaria-hidden属性の付与業務が発生し、都度確認するのは骨が折れます。実装も複雑になりそうでした。
というわけでここは、aria-modal属性に任せることにしました。
<div role={'dialog'} aria-modal={true} aria-labelledby={'menu'}>
<nav id={'menu'} ref={ref} tabIndex={-1} aria-label={'メインメニュー'}>
・・・
</nav>
</div>
role="dialog"を追加することで、その要素をダイアログ要素として扱ってくれるようになります。
また、aria-modal=“true”を追加することで、その要素以外を非インタラクティブ要素として扱ってくれて、スクリーンリーダーによるアクセスの制御を自動で対応されます。
とても便利ですね!
この便利なaria-modal属性ですが、この記事執筆時点でFireFoxがサポートされていないのと、支援技術の対応も追いついてないものがある点がネックです。
aria-hidden属性を使用する方法が確実ではありますが、さほどサポートが壊滅的な状態ではないと判断し、aria-modal属性を利用しました。ここは議論する余地がありそうですね。
最後に、ESCキー押下でもメニューを閉じられるようにしました。
以下はトグルボタンで使用しているカスタムフックの一部です。
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setIsOpen(false);
}
};
window.addEventListener('keydown', handleKeyDown);
}, [setIsOpen]);
これで、キーボード、スクリーンリーダーで操作できるメニューになりました。🎉
以上でグローバルナビゲーションとSPAにおけるアクセシビリティ向上内容は終わりです。
今後取り組みたいこと
自社コーポレートサイトのアクセシビリティ向上の取り組みは今後も継続して行いたいです。
現時点で考えているのが、お問い合わせページのアクセシビリティ向上です。
実際にユーザーが操作する場所なので難しい部分ではありますが、一歩ずつ向上させたいと思います。
また、今回のアクセシビリティ向上は一人で取り組みました。いずれはチームで取り組めるような環境作りをしたいです。
具体的には、LintやCIの導入でアクセシビリティテストの自動化をし、マージされた段階でアクセシビリティの問題がある程度抑制されている状態の仕組み作りをしたいと考えています。
終わりに
初めてのアクセシビリティ向上の取り組みは予想以上に大変でした。
一歩ずつですが向上を継続し、アクセシブルなWebサイト、Webアプリケーションの構築が実現できるように取り組んでいきます。
イチマルイチデザイン株式会社では、WebアプリケーションやWebサイトの開発をするフロントエンドエンジニアの仲間を募集しています。
デザイナーやバックエンドエンジニアの方も募集をしておりますので、興味がある方はご連絡お待ちしております。
▼採用サイトはこちら