アクセシブルなプロダクトを提供するための取り組み
はじめに
こんにちは。最近、食パンのおいしさに改めて気づいて感動している wozitto です。
マネーフォワード福岡開発拠点にて、マネーフォワード クラウド経費(以降、クラウド経費とする)のフロントエンド分離プロジェクトに取り組んでいます。
フロントエンド分離プロジェクトとは
まず、フロントエンド分離プロジェクトについて簡単にご説明します。
クラウド経費は2016年2月にリリースされたサービス [1] で、当時は適切であった技術的な選択が現在では古くなるなど、いわゆる技術的な負債が溜まっている状況です。
この技術的な負債を解消し、既存のRailsアプリケーションからフロントエンドを「分離」するために立ち上がったプロジェクトが、フロントエンド分離プロジェクトです。
具体的には、既存のRailsアプリケーションとは別にNext.jsを用いたアプリケーションを構築し、フロントエンドを画面ごとに置き換えようとしています。
フロントエンド分離プロジェクトでは、技術的負債の解消だけでなく、「アクセシビリティの改善」や「自動テストのカバレッジ80%達成」、「App Routerの採用」など、様々な挑戦を行っています。
今回はその中でも、「アクセシビリティの改善」のために行っている取り組みを「実装編」と「勉強編」に分けて紹介していこうと思います。
なお、フロントエンド分離プロジェクトは現在鋭意進行中です。高品質なプロダクトを提供することを目指して中長期的プロジェクトとして取り組んでいます。
想定する読者
- フロントエンドエンジニア
- ウェブアクセシビリティに関心がある方
アクセシビリティとは
「アクセシビリティ」は「利用可能な度合いや状況の幅広さ」を指します。
「アクセシビリティ」と似たような使われ方をする言葉として、「ユーザビリティ」があります。「ユーザビリティ」は「特定のユーザの特定の利用状況における使いやすさ」を指します。
どちらも「使いやすさ」という共通の意味を持ち明確に区分できるものではないのですが、「アクセシビリティ」はより広い条件下での「使いやすさ」を指します。
『見えにくい、読みにくい「困った!」を解決するデザイン』p.23より転載
より多くの方々に価値のあるサービスを届けていくためにも、アクセシビリティの向上は必要不可欠だと思っています。
実装編
導入しているツールやユニットテストでのアクセシビリティの確認など、実装レベルでの取り組みを紹介します。
Money Forward UIの導入
マネーフォワードでは、Webプロダクト向けの「UI開発基盤」として、Money Forward UIというデザインシステムを開発しています。
これは現在、マネーフォワード クラウド(MFC)[2]という30を超えるプロダクトからなるプロダクトシリーズを対象に開発されています。複数のサービスで一貫性のあるUIを使用することで、プロダクト間の見た目や振る舞いの差異を減らし、ユーザーの認知コストを削減することを目指しています。
これにより、マネーフォワード クラウド全体で一貫した使用感を提供し、併用体験を向上させることができると考えています。
開発者にとっても、プロダクトごとにデザインを考える手間やUI設計の議論、自前でコンポーネントを作成する手間が減り、開発効率の向上に寄与します。
さらに、Money Forward UIはアクセシビリティにも配慮しており、スクリーンリーダーによる読み上げやキーボードのみでの操作性の確保など、プロダクトごとに実装するには工数がかかる部分も、Money Forward UIで提供されているコンポーネントを使用することで、アクセシビリティを担保することができます。
詳細は、Money Forward UI開発チームに所属する kiyokawaさん が発表した以下のスライドをご覧ください。
Money Forward UIの使用例
Money Forward UIはGitHub Packageで社内に展開されているため、以下のように使用することができます。
npm install @moneyforward/mfui-components
import '@moneyforward/mfui-components/styles.css';
import { useState } from 'react';
import { Button } from '@moneyforward/mfui-components';
import { Dialog } from '@moneyforward/mfui-components';
export const ConfirmationDialog = () => {
const [isOpen, setIsOpen] = useState(false);
const handleOpen = () => setIsOpen(true);
const handleClose = () => setIsOpen(false);
return (
<>
<Button onClick={handleOpen}>確認ダイアログを開く</Button>
<Dialog
title="確認"
hasDialogOpened={isOpen}
onCloseDialog={handleClose}
actions={
<>
<Button onClick={handleClose}>キャンセル</Button>
<Button priority="primary" onClick={handleClose}>
OK
</Button>
</>
}
>
変更を保存してもよろしいですか?
</Dialog>
</>
);
};
出力結果
Radix UIの導入
Money Forward UIは現在実装中であり、提供されているコンポーネントはまだ完全ではありません。また、プロダクト特有のコンポーネントも存在するため、それらを自前で実装する必要があります。このため、フロントエンド分離プロジェクトでは、コンポーネントライブラリとして Radix UI を採用しています。(Money Forward UIで提供されたコンポーネントは、随時置き換えていく予定です。)
Radix UIは、いわゆるヘッドレスUIと呼ばれる、見た目を提供せずUIの振る舞いのみを提供するコンポーネントライブラリです。
クラウド経費では、デザインシステムの推進に合わせるため、MUI や Chakra UI などの見た目まで提供するライブラリではなく、見た目は自社でカスタマイズ可能なヘッドレスUIからの選定としました。
Radix UIは、WAI-ARIA authoring practices guidelines に従って実装されており、アクセシブルな名前付けやキーボードナビゲーション、フォーカス管理などの基本的なアクセシビリティをライブラリ側である程度担保してくれます。
これにより、アクセシビリティに関する実装の一部をライブラリに委ねることができ、実装を簡潔にすることができます。
ただし、実際に要素に適切な名前が指定されているか、キーボードナビゲーションが可能かどうかなどは出力されるまで分からないところもあるので、ブラウザ上での目視確認や、スクリーンリーダーなどの支援技術をを用いた確認を行うようにしています。
eslint-plugin-jsx-a11yとMarkuplintの導入
フロントエンド分離プロジェクトでは、 eslint-plugin-jsx-a11y と Markuplint を導入しています。
eslint-plugin-jsx-a11yは、JSXにおけるアクセシビリティの問題を検証するESLintプラグインです。
一方、Markuplintは、HTMLマークアップがWAI-ARIAの仕様に準拠しているか、アクセシビリティ上の問題がないかなどを検証するHTMLリンターです。
これらのツールは一部重複する機能を持っていますが、それぞれで固有の機能もあります。両方のツールを組み合わせることで、より網羅的にアクセシビリティ上の問題を検証することができます。
例えば、img要素にアクセシブルな名前の指定がない場合、eslint-plugin-jsx-a11yとMarkuplintは以下のようなエラーを表示してくれます。
これにより、不適切な実装やマークアップを機械的に検出することができ、開発者のスキルに依らず一定の品質を保ったマークアップができるようになります。
また、これらのツールが指摘したエラーの理由を調べることで、HTMLの仕様について学習することにもつながります。
フロントエンド分離プロジェクトでは以下のように、eslint-plugin-jsx-a11yとMarkuplintが推奨しているルールやプリセットを利用しています。
{
"extends": ["plugin:jsx-a11y/recommended"]
}
{
"extends": ["markuplint:recommended-react"]
}
@axe-core/playwrightの導入
フロントエンド分離プロジェクトでは、@axe-core/playwright を導入しています。
@axe-core/playwrightについて説明する前に、Playwright とaxe-core について簡単に説明します。
まず、Playwrightは、E2Eテストを行うためのツールです。実際にブラウザに表示された環境で要素の確認やAPI通信の検証を行うことができます。
axe-coreは、アクセシビリティ検証ツールである axe で使用されているコアエンジンで、Lighthouse に搭載されていることでも有名です。
このaxe-coreをPlaywright上で使用できるようにしたものが、@axe-core/playwright です。
@axe-core/playwrightを使用することで、ページを構成する複数のコンポーネントが統合された状態でのアクセシビリティをテストできます。
また、個人的には手動での確認が大変なカラーコントラストのチェックを機械的に行えるようになることも、大きな恩恵だと感じています。
具体的には以下のような実装で、@axe-core/playwrightによるアクセシビリティテストを行うことができます。実装例は Playwright公式のAccessibility testing を参考にしています。
import { test, expect } from '@playwright/test';
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright'; // @axe-core/playwrightパッケージをimport
import { violationFingerprints } from './violationFingerprints';
test.describe('homepage', () => {
test('accessibilityScanResults snapshot', async ({ page }) => {
await page.goto('https://example.com'); // テスト対象ページに移動
// AxeBuilder APIを使用して対象のページに対してアクセシビリティスキャンを実行
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
// スナップショットテストを実行し、前回のテスト実行時からアクセシビリティ上の問題が増減していないかを確認
// 問題が発生または解消された場合、スナップショットが一致せずテストが失敗する
expect(violationFingerprints(accessibilityScanResults)).toMatchSnapshot();
});
});
検出されたエラーに関しては、修正方法やデザインの調整など別途議論を行う必要があるので、以下のような手順を踏んで少しずつ対応しています。
- 検出されたエラーの修正対応issueを起票する。
- 修正対応が完了したら、テストを再実行してスナップショットを更新し、問題が解消されたことを確認します。
アクセシブルなプロダクトを提供している他社のウェブサイトやデザインシステムを参考にする
これは個人的に取り組んでいることの紹介になるのですが、筆者はコンポーネント開発時にデジタル庁や GOV.UK などの行政機関が提供しているウェブサイトや、W3C やSmartHR などが公開しているデザインシステムを参考にしています。
行政機関が提供しているウェブサイトは、公共性が求められるため、国が提供しているサービスをできる限り多くの利用者に使ってもらえたり、情報を取得したりできるように高いアクセシビリティを確保していることが多いです。
なので行政機関が提供しているウェブサイトに、開発しているコンポーネントと関連するUIがあれば、DevToolsを使用して要素の確認を行い、マークアップなどの情報設計を参考にしています。
W3CやSmartHRなどが公開しているデザインシステムでは、様々なコンポーネントの設計を確認することができます。
そこではマークアップだけでなく、コンポーネントの機能を表現するためのARIA属性の指定なども確認できます。
また、公開されているPropsの設計から、コンポーネントの責務や汎用性をどこまで持たせるかなどのコンポーネント設計上の知見を得ることもできます。
アクセシビリティテストの実装
フロントエンド分離プロジェクトでは、Jest とTesting Library を使用してテストを実装しています。
テストは「機能要件テスト」「非機能要件テスト」「スナップショットテスト」の3つに大別され、その中の「非機能要件テスト」でアクセシビリティに関するテストを実施しています。
アクセシビリティテストでは、以下3つの点を確認しています。
- UIが適切なロールを持っていること
- キーボード操作が可能であること
- WAI-ARIA属性が意図した出力になっていること
「UIが適切なロールを持っていること」では、コンポーネントをレンダリングして生成される要素が意図したロールを持っているかを検査しています。
検査には、プロジェクト内で実装したJestのカスタムマッチャーであるtoContainRolesEqualTo
を使用しています。
toContainRolesEqualTo
は、第一引数で指定した配列内のロールが全て存在し、かつ指定したロール以外のロールが存在しないことを検査します。
これにより、UIが期待通りのロールのみを持ち、不要なロールを持っていないことを簡潔に検査することができます。
「キーボード操作が可能であること」では、UIの選択や入力といった基本的な操作がキーボードで行えることを確認しています。また、キーボード操作のために特別な実装をした場合やバグ報告等を起因とした実装がある場合には、それらに対してのテストも実装しています。
「WAI-ARIA属性が意図した出力になっていること」では、意図した情報を伝えるためにaria-label
やaria-current
などのWAI-ARIA属性を追加した場合、それらの属性が適切に出力されているかを確認しています。
アクセシビリティテストの具体的な実装例は以下の通りです。
describe('非機能要件', () => {
describe('アクセシビリティテスト', () => {
describe('ロールテスト', () => {
test('"navigation"、"list"、"listitem"、"link"のロールを含んでいること', () => {
const { container } = render(
<Pagination perPage={25} total={200} currentPage={5} />,
);
expect(container).toContainRolesEqualTo([
'navigation',
'list',
'listitem',
'link',
]);
});
});
test('[Tab]キーを使用してリンクにフォーカスできること', async () => {
render(<Pagination perPage={25} total={250} currentPage={5} />);
const user = userEvent.setup();
const firstLink = screen.getByRole('link', { name: '最初のページ' });
await user.tab();
expect(firstLink).toHaveFocus();
});
test('現在のページのリンクには`aria-current="page"`属性があること', () => {
render(<Pagination perPage={25} total={250} currentPage={5} />);
expect(screen.getByRole('link', { name: 'ページ 5' })).toHaveAttribute(
'aria-current',
'page',
);
});
});
});
});
勉強編
次に、アクセシビリティの知識を身につけるためにチームで行っている取り組みを紹介します。
チームでのアクセシビリティガイドライン輪読会
当社では、Money Forward Cloud Accessibility GuidelinesがGitHubに公開されており、社内向けに共有されています。弊社は英語化を進めているため、このガイドラインは英語で書かれています。
このガイドラインは、Web Content Accessibility Guidelines (WCAG) 2.2 [3]をベースとして、基準の概要、デザイン、実装、動作確認におけるポイントを各達成基準ごとにまとめたものです。
このガイドラインの輪読会をチームで行っており、以下のように進めています。
- ガイドラインから一つのセクションを選び、一文ずつ輪読していく。
- 適宜、英語の文法やアクセシビリティの実装例について気になる点があれば議論する。
- 最後に、輪読したセクションに関連する実装の確認を行う。
- 既存の実装とフロントエンド分離プロジェクトでの実装を比較して、アクセシビリティが改善されたか、追加で行う必要のある実装はないかなどを確認しています。
そして、輪読会で英文や実装に改善点が見つかった場合は、より良いアクセシビリティガイドラインにするために当事者意識を持ってPRの作成やコメントなどを行うようにしています。
デザイナーとの定期的なミーティング
アクセシビリティ対応は、エンジニアのみが担当するものではありません。プロダクトのデザイン段階から、情報設計上の配慮や、カラーコントラスト、文字の大きさなどの視覚表現におけるアクセシビリティに配慮する必要があります。
そのため、デザイナーとエンジニアでアクセシビリティに対して共通の理解を持つことは、プロダクトの品質を高める上でも重要です。
しかし、デザイナーとエンジニアはそれぞれ異なる視点からプロダクトを捉えるため、定期的なミーティングで両者の視点のすり合わせを行うことが、アクセシビリティ対応のためにも不可欠だと感じています。
デザイナーとの定期的なミーティングでは以下のようなことを行っています。
- ロードマップの共有
- 実装した画面の全体的なレビューと課題の洗い出し
- アクセシビリティガイドラインの輪読会
「ロードマップの共有」では、デザイナーとエンジニアで直近1年くらいのロードマップを共有して、いつ、どの程度のリソースを確保することができるのか、どの程度のスピード感を求められているのかなどを確認しています。
フロントエンド分離プロジェクトのデザイナーは専任ではなく、他のプロジェクトも兼務しています。そのため、フロントエンド分離プロジェクトと他プロジェクトとでリソースをうまく調整する必要があります。
「実装した画面の全体的なレビューと課題の洗い出し」では、フロントエンド分離プロジェクトで実装した画面の全体レビューを行い、主に既存の画面と分離後の画面で機能の欠陥やデザインの崩れが起きていないかを確認しています。
また、このタイミングでデザイナーに依頼するデザインタスクや相談があれば一緒に行っています。
「アクセシビリティガイドラインの輪読会」では、Money Forward Cloud Accessibility Guidelinesの日本語訳を用いて、輪読会を行っています。
進め方としては、miroにガイドラインの記事の画像を貼り、最初に各自で読み進める時間を設け、気になった点や質問を付箋に記入して貼っていきます。その後、付箋の内容を確認しながら全員で読み進めていくといったことをやっています。
デザイナーはプロダクトをデザインやUXなどのビジュアルの観点から捉えるため、「アイコンのみの要素の場合は、その要素の機能を示す名前をaria-label
で指定する必要がある」などの実装レベルでのアクセシビリティ対応を知らないことがあります。
ユーザーのコンテンツへのアクセス手段に関わらず、情報や情報の関係性を認識できるようにするには、デザインという視覚的な表現を、プログラムでも認識可能な状態(マシンリーダブル)にする必要があります。
そのため、デザインの意図した情報や情報の関係性をプログラム的に認識可能にするためにはどのようなマークアップ、名前付けを行えば良いのか、といったことを実際のプロダクトを確認しながら議論しています。
終わりに
アクセシブルなプロダクトを提供するためには専門的なスキルが必要であり、アクセシビリティ対応を開発プロセスに組み込んでいくためには、組織全体にアクセシビリティの重要性を理解してもらう必要があります。そのための外交的なコミュニケーションや啓発活動も欠かせません。
そして、全ての人が全ての機能を利用できるプロダクトを提供するためには、長い道のりが必要だと感じます。
しかし、プロダクトに関わる一人一人がアクセシビリティに対する意識を持ち、まずは自分のできる範囲から小さく取り組んでいくことが、置かれている状況や環境に関わらず、全ての人が全てのサービスを利用できる優しい社会への歩みになっていると信じています。
本記事で紹介した、アクセシブルなプロダクトを提供するために行っている取り組みが、ウェブアクセシビリティに取り組んでいる方々の参考になれば幸いです。
Discussion