✍️ 필사 모드: アクセシビリティと国際化 2025 — WCAG 2.2·EU Accessibility Act·ARIA·next-intl·日韓タイポグラフィ·RTL (シーズン6 第7回)
日本語プロローグ — 「パフォーマンスは良いのに使えない」
Core Web Vitalsが緑でも、スクリーンリーダーでリストが読めず、韓国語で助詞が崩れて改行が不自然なサービスがある。「最初から設計すべきだった」と気付く瞬間。
2025年の転換点3つ:
- EU Accessibility Act 発効 (2025-06-28) — EU市場向けデジタル製品 (Web·アプリ·EC·電子書籍·ATM·交通端末) にWCAG義務。違反 → 市場撤退。
- WCAG 2.2 W3C勧告 (2023-10) — タッチターゲット最小サイズ、ドラッグ代替、認証認知負荷。2025年公共·大企業調達基準。
- AI翻訳の質向上。但し助詞·敬語·文化的文脈は依然人間必須。
日本ではJIS X 8341 + 障害者差別解消法、韓国では障害者差別禁止法 + 電子政府法。これは品質投資 — SEO + 離脱減 + 新規獲得 + 法的リスク解消。
1章. 再定義 — 「全員の品質」
2025年定義は6状況を包括:
- 恒久的 — 視覚·聴覚·運動·認知障害。
- 一時的 — 腕の骨折、術後、薬副作用。
- 状況的 — 日光下、騒音、片手が子供。
- 高齢化 — 徐々の感覚低下。
- デバイス多様性 — 小型·巨大·低スペック·遅いネットワーク。
- 言語·文化 — 非英語、少数言語、識字率差。
永久障害は約16% (13億人)。6状況を合わせればほぼ全員がいずれかの時点で必要。
アクセシビリティはSEO·転換率·離脱率も改善。Google調査: 完全な現地化で購入意図72%増。
2章. WCAG 2.2 — 2025年基準
POUR原則
- Perceivable — 全情報知覚可能。
- Operable — 全コントロール操作可能。
- Understandable — 情報·操作が理解可能。
- Robust — 支援技術が解釈可能。
レベル
- A 最小、AA 業界標準 (EU Act·JIS AA·US 508)、AAA 高水準。
WCAG 2.2の新基準 (最も影響大)
- 2.4.11 Focus Not Obscured (Min) — Sticky Headerがフォーカスを隠してはならない。
- 2.4.13 Focus Appearance —
:focus-visible最小コントラスト·サイズ。 - 2.5.7 Dragging Movements — ドラッグ専用UIに代替 (Kanban)。
- 2.5.8 Target Size (Min) — 24×24 CSS px最小。
- 3.2.6 Consistent Help — ヘルプ·連絡先の一貫した場所。
- 3.3.7 Redundant Entry — 既入力情報の再要求禁止。
- 3.3.8 Accessible Authentication — 認知パズルなし代替 (Passkey/WebAuthn普及)。
ターゲットサイズはモバイルのアイコンボタン·閉じる (×) ボタンのほぼ全てに影響。
3章. セマンティックHTML — <div> 共和国から脱出
支援技術はセマンティクスを読む。<div> だけでは何も伝わらない。
Bad
<div class="button" onclick="submit()">確認</div>
<div class="list"><div class="item">項目1</div></div>
Good
<header>
<nav aria-label="主要">
<ul><li><a href="/">ホーム</a></li></ul>
</nav>
</header>
<main>
<article><h1>タイトル</h1><p>本文</p></article>
</main>
<button type="submit">確認</button>
<ul><li>項目1</li></ul>
スクリーンリーダーがランドマーク、リストカウント、ボタンロール、ヘディングナビを自動アナウンス。
<button> vs <a>
動作 → <button>、ナビゲーション → <a href>。入れ替えない。
4章. ARIA — 強力だが危険
"No ARIA is better than bad ARIA."
使う場面
- ネイティブHTMLで表現不可 (タブ、コンボボックス、ツリー)。
- 動的状態 (
aria-expanded、aria-selected、aria-busy)。 - 名前·説明 (
aria-label、aria-labelledby、aria-describedby)。 - ライブリージョン (
aria-live)。
定番パターン
<button aria-label="メニューを閉じる"><svg aria-hidden="true">...</svg></button>
<button aria-expanded="false" aria-controls="menu-1">メニュー</button>
<ul id="menu-1" hidden>...</ul>
<div aria-live="polite">保存しました。</div>
<div aria-live="assertive" role="alert">エラー: パスワード違い</div>
<input aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" role="alert">正しいメールを入力</span>
アンチパターン
<div role="button">—<button>使う。role="list"+<div>—<ul>使う。aria-hidden="true"on<main>— 全体が消える。- フォーカス可能要素に
aria-hidden— 矛盾状態。 - 情報性画像に
aria-label="画像"—<img alt>を使う。
5章. キーボードナビゲーション
マウスを30分片付けるだけで問題が露呈。
必須キー
Tab、Shift+Tab、Enter/Space、Esc、ウィジェット内方向キー、Home/End。
フォーカス可視 — :focus-visible
button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
button:focus:not(:focus-visible) { outline: none; }
フォーカストラップ
ネイティブ <dialog> + showModal() 自動処理。カスタムモーダルは focus-trap ライブラリ。
スキップリンク
<a href="#main" class="skip-link">本文へスキップ</a>
オフスクリーン、フォーカス時のみ可視。
6章. スクリーンリーダーテスト
| SR | プラットフォーム | シェア (WebAIM) |
|---|---|---|
| NVDA | Windows | 67% |
| JAWS | Windows | 30% |
| VoiceOver | macOS/iOS | 内蔵 |
| TalkBack | Android | 内蔵 |
VoiceOverショートカット (macOS)
Cmd+F5トグル。Ctrl+Option+矢印ナビ。Ctrl+Option+Uローター (ランドマーク·見出し·リンク)。
テストチェックリスト
- Tabで全インタラクティブ要素到達可?
- フォーカス順と視覚順が一致?
- ロールが正確にアナウンス?
- ラベルがinputに紐付け?
- エラーメッセージがライブアナウンス?
- モーダルのフォーカス管理OK?
- 動的コンテンツがアナウンス?
7章. 色·コントラスト·モーション
コントラスト (AA)
- 通常 4.5:1、大テキスト 3:1、UI境界·アイコン 3:1。
色のみで情報伝達禁止
色 + アイコン + テキスト。
prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
200%拡大
rem/em使用、max-width で行長制限、重要テキストに固定px禁止。
8章. フォームのアクセシビリティ
ラベルとinputをbind
<label for="email">メール</label>
<input id="email" type="email" required />
プレースホルダはラベルではない
入力で消える、コントラスト不足、記憶依存強要。別途ラベル必須。
エラーメッセージ
<input id="pw" aria-invalid="true" aria-describedby="pw-help pw-error" />
<span id="pw-help">8文字以上 英字+数字</span>
<span id="pw-error" role="alert">パスワードが短すぎます</span>
Autocomplete
autocomplete="email" | "tel" | "current-password" | "new-password" | "name" | "postal-code" — パスワードマネージャ·支援技術に有益。WCAG 1.3.5。
9章. i18n基礎
- i18n — ロケール対応可能な設計。
- l10n — 実際の翻訳·現地化。
- g11n — ビジネス戦略。
5軸
- テキスト翻訳。
- 書式 (数値·日付·通貨)。
- 照合 (ソート順)。
- 方向 (LTR/RTL)。
- レイアウト (ドイツ語は30%長い)。
2025年フレームワーク
- next-intl — Next.js App Routerデファクト。
- react-intl / FormatJS — React + ICU。
- i18next、vue-i18n、Svelte-i18n / Paraglide、Lingui、@angular/localize。
10章. next-intl実戦
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ requestLocale }) => {
const locale = (await requestLocale) ?? 'ja'
return { locale, messages: (await import(`../messages/${locale}.json`)).default }
})
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'
export default async function Layout({ children, params }) {
const { locale } = await params
const messages = await getMessages()
return <html lang={locale}><body><NextIntlClientProvider locale={locale} messages={messages}>{children}</NextIntlClientProvider></body></html>
}
import { getTranslations } from 'next-intl/server'
export default async function Home() {
const t = await getTranslations('home')
return <main><h1>{t('title')}</h1></main>
}
ICU MessageFormat
{
"cart": "{count, plural, =0 {空です} one {# 個} other {# 個}}",
"welcome": "{name}さん、こんにちは!"
}
複数形·性別·選択をメッセージで処理、JSロジック不要。
11章. Locale-Awareフォーマット — Intl API
Intl.NumberFormat
new Intl.NumberFormat('ja-JP').format(1234567) // "1,234,567"
new Intl.NumberFormat('de-DE').format(1234567) // "1.234.567"
new Intl.NumberFormat('hi-IN').format(1234567) // "12,34,567" — lakh/crore
new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(29000) // "¥29,000"
Intl.DateTimeFormat、Intl.RelativeTimeFormat、Intl.ListFormat、Intl.Collator — 手書きしない。Intl + Temporal API (Stage 3) を使う。
12章. 日韓の特殊事項
韓国語助詞
function suffix(word: string, withBatchim: string, without: string) {
const last = word.charCodeAt(word.length - 1)
const hasBatchim = (last - 0xac00) % 28 !== 0
return hasBatchim ? withBatchim : without
}
// suffix('사과', '을', '를') → '을'
Toss の es-hangul が助詞処理·字母分解をまとめて提供。
韓国語改行
body { word-break: keep-all; overflow-wrap: break-word; line-break: strict; }
keep-all がCJKで単語途中改行を防ぐ。2025年全ブラウザ対応。
日本語縦書き (tategaki)
.tategaki { writing-mode: vertical-rl; text-orientation: mixed; font-family: 'Yu Mincho', serif; }
CJKフォント最適化
- 韓国: Pretendard (7MB → 100KBサブセット)。
- 日本: Noto Sans JP、Yu Gothic。
- 中国 (簡·繁): Noto Sans SC/TC。
- サブセット必須。
RTL
<html lang="ar" dir="rtl">...</html>
論理CSSプロパティ (margin-inline-start、padding-inline-end) — 自動LTR/RTL対応。
13章. チェックリスト + アンチパターン
出荷前a11yチェックリスト (15)
- 全画像に意味ある
alt(装飾はalt="")。 - 全inputにラベル。
- コントラスト ≥ 4.5:1。
- ページに1
<h1>、見出しスキップなし。 - 全機能キーボードで操作可。
:focus-visible可視。- タッチターゲット ≥ 24×24。
- モーダルのフォーカストラップ + Esc。
- スキップリンク。
prefers-reduced-motion対応。- 動的コンテンツに
aria-live。 - Lighthouse a11yスコア100。
- axe-core/Pa11y CI統合。
- 手動SRテスト。
- 障害のある実ユーザーテスト。
TOP 10 a11y/i18nアンチパターン
<div onclick>乱発。outline: none+:focus-visibleなし。- プレースホルダをラベル代わり。
alt="画像"(冗長)。- フォーカス可能要素に
aria-hidden。 - 色のみで情報伝達。
- 自動再生メディア。
"こんにちは " + name文字列結合 (助詞·語順破綻)。- 日付·通貨の手書きフォーマット (Intl使う)。
- Google Translateで一括、レビューなしで配信。
次回予告
シーズン6 第8回: フロントエンドモニタリング·エラートラッキング 2025 — Sentry、Datadog RUM、PostHog、LogRocket、Session Replay、Source Map、AI異常検知。
「アクセシビリティは少数への配慮ではなく、全員が使える基本。i18nは翻訳ではなく、違うリズムで生きる人々への敬意」
— アクセシビリティと国際化編、完。
현재 단락 (1/176)
Core Web Vitalsが緑でも、スクリーンリーダーでリストが読めず、韓国語で助詞が崩れて改行が不自然なサービスがある。「最初から設計すべきだった」と気付く瞬間。