Skip to content
Published on

アクセシビリティと国際化 2025 — WCAG 2.2·EU Accessibility Act·ARIA·next-intl·日韓タイポグラフィ·RTL (シーズン6 第7回)

Authors

プロローグ — 「パフォーマンスは良いのに使えない」

Core Web Vitalsが緑でも、スクリーンリーダーでリストが読めず、韓国語で助詞が崩れて改行が不自然なサービスがある。「最初から設計すべきだった」と気付く瞬間。

2025年の転換点3つ:

  1. EU Accessibility Act 発効 (2025-06-28) — EU市場向けデジタル製品 (Web·アプリ·EC·電子書籍·ATM·交通端末) にWCAG義務。違反 → 市場撤退。
  2. WCAG 2.2 W3C勧告 (2023-10) — タッチターゲット最小サイズ、ドラッグ代替、認証認知負荷。2025年公共·大企業調達基準。
  3. AI翻訳の質向上。但し助詞·敬語·文化的文脈は依然人間必須。

日本ではJIS X 8341 + 障害者差別解消法、韓国では障害者差別禁止法 + 電子政府法。これは品質投資 — SEO + 離脱減 + 新規獲得 + 法的リスク解消。


1章. 再定義 — 「全員の品質」

2025年定義は6状況を包括:

  1. 恒久的 — 視覚·聴覚·運動·認知障害。
  2. 一時的 — 腕の骨折、術後、薬副作用。
  3. 状況的 — 日光下、騒音、片手が子供。
  4. 高齢化 — 徐々の感覚低下。
  5. デバイス多様性 — 小型·巨大·低スペック·遅いネットワーク。
  6. 言語·文化 — 非英語、少数言語、識字率差。

永久障害は約16% (13億人)。6状況を合わせればほぼ全員がいずれかの時点で必要。

アクセシビリティはSEO·転換率·離脱率も改善。Google調査: 完全な現地化で購入意図72%増。


2章. WCAG 2.2 — 2025年基準

POUR原則

  1. Perceivable — 全情報知覚可能。
  2. Operable — 全コントロール操作可能。
  3. Understandable — 情報·操作が理解可能。
  4. 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."

使う場面

  1. ネイティブHTMLで表現不可 (タブ、コンボボックス、ツリー)。
  2. 動的状態 (aria-expandedaria-selectedaria-busy)。
  3. 名前·説明 (aria-labelaria-labelledbyaria-describedby)。
  4. ライブリージョン (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>

アンチパターン

  1. <div role="button"><button> 使う。
  2. role="list" + <div><ul> 使う。
  3. aria-hidden="true" on <main> — 全体が消える。
  4. フォーカス可能要素に aria-hidden — 矛盾状態。
  5. 情報性画像に aria-label="画像"<img alt> を使う。

5章. キーボードナビゲーション

マウスを30分片付けるだけで問題が露呈。

必須キー

TabShift+TabEnter/SpaceEsc、ウィジェット内方向キー、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)
NVDAWindows67%
JAWSWindows30%
VoiceOvermacOS/iOS内蔵
TalkBackAndroid内蔵

VoiceOverショートカット (macOS)

  • Cmd+F5 トグル。
  • Ctrl+Option+矢印 ナビ。
  • Ctrl+Option+U ローター (ランドマーク·見出し·リンク)。

テストチェックリスト

  1. Tabで全インタラクティブ要素到達可?
  2. フォーカス順と視覚順が一致?
  3. ロールが正確にアナウンス?
  4. ラベルがinputに紐付け?
  5. エラーメッセージがライブアナウンス?
  6. モーダルのフォーカス管理OK?
  7. 動的コンテンツがアナウンス?

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軸

  1. テキスト翻訳。
  2. 書式 (数値·日付·通貨)。
  3. 照合 (ソート順)。
  4. 方向 (LTR/RTL)。
  5. レイアウト (ドイツ語は30%長い)。

2025年フレームワーク

  • next-intl — Next.js App Routerデファクト。
  • react-intl / FormatJS — React + ICU。
  • i18nextvue-i18nSvelte-i18n / ParaglideLingui@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.DateTimeFormatIntl.RelativeTimeFormatIntl.ListFormatIntl.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-startpadding-inline-end) — 自動LTR/RTL対応。


13章. チェックリスト + アンチパターン

出荷前a11yチェックリスト (15)

  1. 全画像に意味ある alt (装飾は alt="")。
  2. 全inputにラベル。
  3. コントラスト ≥ 4.5:1。
  4. ページに1 <h1>、見出しスキップなし。
  5. 全機能キーボードで操作可。
  6. :focus-visible 可視。
  7. タッチターゲット ≥ 24×24。
  8. モーダルのフォーカストラップ + Esc。
  9. スキップリンク。
  10. prefers-reduced-motion 対応。
  11. 動的コンテンツに aria-live
  12. Lighthouse a11yスコア100。
  13. axe-core/Pa11y CI統合。
  14. 手動SRテスト。
  15. 障害のある実ユーザーテスト。

TOP 10 a11y/i18nアンチパターン

  1. <div onclick> 乱発。
  2. outline: none + :focus-visible なし。
  3. プレースホルダをラベル代わり。
  4. alt="画像" (冗長)。
  5. フォーカス可能要素に aria-hidden
  6. 色のみで情報伝達。
  7. 自動再生メディア。
  8. "こんにちは " + name 文字列結合 (助詞·語順破綻)。
  9. 日付·通貨の手書きフォーマット (Intl使う)。
  10. Google Translateで一括、レビューなしで配信。

次回予告

シーズン6 第8回: フロントエンドモニタリング·エラートラッキング 2025 — Sentry、Datadog RUM、PostHog、LogRocket、Session Replay、Source Map、AI異常検知。

「アクセシビリティは少数への配慮ではなく、全員が使える基本。i18nは翻訳ではなく、違うリズムで生きる人々への敬意」

— アクセシビリティと国際化編、完。