- Published on
Accessibility & Internationalization 2025 — WCAG 2.2, EU Accessibility Act, ARIA, next-intl, Korean/Japanese Typography, RTL (S6 E7)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Prologue — "performance looks great, but it's unusable"
Core Web Vitals can be bright green while the screen reader cannot read a list and Korean versions break on particles and line breaks. That's when the team says: "we should have designed this from the start."
Three 2025 inflection points:
- EU Accessibility Act (effective 2025-06-28) — digital products in EU (web, app, ecommerce, e-books, ATMs, transit) must meet WCAG. Non-compliance → withdrawal from market.
- WCAG 2.2 W3C Recommendation (2023-10) — touch target sizes, drag alternatives, cognitive-load auth. 2025 public / enterprise procurement baseline.
- AI-assisted translation reached "good enough", but Korean particles, Japanese honorifics, and cultural nuance still need humans.
Korea enforces Anti-Discrimination Against Disabled Persons Act + e-Gov Act for public/finance/education. This is quality investment: SEO + retention + new users + lower legal risk.
1. Redefining accessibility — "quality for everyone"
2025 definition covers six situations:
- Permanent — vision, hearing, motor, cognitive.
- Temporary — broken arm, post-surgery, medication side effects.
- Situational — sunlight, noise, one hand holding a baby.
- Aging — gradual sensory decline.
- Device diversity — small phones, huge TVs, low-end devices, slow networks.
- Language / culture — non-English, minor languages, literacy.
~16% (1.3B) have permanent disabilities. Counting the rest — nearly everyone needs accessibility at some moment.
Accessibility also improves SEO, conversion, and reduces churn. Google found that complete localization increases purchase intent by 72%.
2. WCAG 2.2 — the 2025 baseline
POUR principles
- Perceivable — every info perceivable.
- Operable — every control operable.
- Understandable — info & operation.
- Robust — parseable by assistive tech.
Levels
- A — minimum. AA — industry standard (EU Act, KWCAG, US 508 all base on this). AAA — high.
WCAG 2.2 new success criteria (most impactful)
- 2.4.11 Focus Not Obscured (Min) — sticky headers mustn't cover focus.
- 2.4.13 Focus Appearance —
:focus-visibleminimum contrast/size. - 2.5.7 Dragging Movements — non-drag alternative for drag-only UIs (Kanban).
- 2.5.8 Target Size (Min) — 24×24 CSS px minimum.
- 3.2.6 Consistent Help — consistent help/contact location.
- 3.3.7 Redundant Entry — don't re-ask already-entered info.
- 3.3.8 Accessible Authentication — no cognitive-puzzle auth (Passkey/WebAuthn surge).
Target size affects nearly every mobile icon button and close (×).
3. Semantic HTML — escape the <div> republic
Assistive tech reads semantics. <div>s are read as plain text.
Bad
<div class="button" onclick="submit()">Confirm</div>
<div class="list"><div class="item">Item 1</div></div>
Good
<header>
<nav aria-label="Primary">
<ul><li><a href="/">Home</a></li></ul>
</nav>
</header>
<main>
<article><h1>Title</h1><p>Body</p></article>
</main>
<button type="submit">Confirm</button>
<ul><li>Item 1</li></ul>
Screen readers announce landmarks, list counts, button roles, and heading navigation automatically.
<button> vs <a>
- Action →
<button>. Navigation →<a href>. Do not swap.
4. ARIA — powerful, dangerous
"No ARIA is better than bad ARIA."
Use ARIA when
- Native HTML can't express the UI (tabs, combobox, tree).
- Dynamic state (
aria-expanded,aria-selected,aria-busy). - Name/description (
aria-label,aria-labelledby,aria-describedby). - Live regions (
aria-live).
Common patterns
<button aria-label="Close menu"><svg aria-hidden="true">...</svg></button>
<button aria-expanded="false" aria-controls="menu-1">Menu</button>
<ul id="menu-1" hidden>...</ul>
<div aria-live="polite">Saved.</div>
<div aria-live="assertive" role="alert">Error: wrong password</div>
<input aria-invalid="true" aria-describedby="email-error" />
<span id="email-error" role="alert">Enter a valid email</span>
ARIA anti-patterns
<div role="button">— use<button>.role="list"on<div>— use<ul>.aria-hidden="true"on<main>— hides the whole app.aria-hiddenon focusable elements — contradiction.aria-label="image"on informational images — use<img alt>.
5. Keyboard navigation
Unplug your mouse for 30 minutes; problems surface instantly.
Required keys
Tab, Shift+Tab, Enter/Space, Esc, arrow keys within widgets, Home/End.
Focus visibility — :focus-visible
button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
button:focus:not(:focus-visible) { outline: none; }
Focus trap
Use native <dialog>; showModal() auto-traps. For custom modals use focus-trap.
Skip links
<a href="#main" class="skip-link">Skip to main</a>
Offscreen by default, visible on focus.
6. Screen reader testing
| SR | Platform | Share (WebAIM) |
|---|---|---|
| NVDA | Windows | 67% |
| JAWS | Windows | 30% |
| VoiceOver | macOS/iOS | built-in |
| TalkBack | Android | built-in |
VoiceOver shortcuts (macOS)
Cmd+F5toggle.Ctrl+Option+Arrownavigate.Ctrl+Option+Urotor (landmarks/headings/links).
Test checklist
- Tab reaches every interactive element?
- Focus order matches visual order?
- Roles announced correctly?
- Labels bound to inputs?
- Error messages live-announced?
- Modal focus management works?
- Dynamic content announced?
7. Color, contrast, motion
Contrast (AA)
- Normal text 4.5:1, large text 3:1, UI borders/icons 3:1.
Never rely on color alone
Pair color with icon + text label.
prefers-reduced-motion
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
200% zoom
Use rem/em, max-width for line length. No fixed-px critical text.
8. Form accessibility
Label must bind to input
<label for="email">Email</label>
<input id="email" type="email" required />
Placeholder is not a label
Disappears on input, insufficient contrast, memory burden. Always separate label.
Error messaging
<input id="pw" aria-invalid="true" aria-describedby="pw-help pw-error" />
<span id="pw-help">8+ chars, letters + digits</span>
<span id="pw-error" role="alert">Password too short</span>
Autocomplete
autocomplete="email" | "tel" | "current-password" | "new-password" | "name" | "postal-code" — helps password managers and assistive tech. WCAG 1.3.5.
9. i18n basics
- i18n — design to support locales.
- l10n — actual translation/adaptation.
- g11n — combined business strategy.
Five axes
- Text translation.
- Formatting (numbers, dates, currency).
- Collation (sort order).
- Directionality (LTR/RTL).
- Layout (German expands ~30%).
Framework landscape 2025
- next-intl — Next.js App Router de facto.
- react-intl / FormatJS — React + ICU.
- i18next — framework-agnostic.
- vue-i18n, Svelte-i18n / Paraglide, Lingui, @angular/localize.
10. next-intl in practice
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ requestLocale }) => {
const locale = (await requestLocale) ?? 'en'
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 {Empty} one {# item} other {# items}}",
"welcome": "Hello, {name}!"
}
Plurals, gender, select — handled in the message, not in JS.
11. Locale-aware formatting — Intl API
Intl.NumberFormat
new Intl.NumberFormat('en-US').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"
new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(29000) // "₩29,000"
Intl.DateTimeFormat, Intl.RelativeTimeFormat, Intl.ListFormat, Intl.Collator — never manually format dates/lists. Use Intl + Temporal API (Stage 3).
12. Korean / Japanese specifics
Korean particles
// Hardcoded "을(를)" burns users
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's es-hangul bundles particle handling, jamo decomposition.
Korean line breaking
body { word-break: keep-all; overflow-wrap: break-word; line-break: strict; }
keep-all avoids mid-word breaks in CJK. Supported everywhere in 2025.
Japanese vertical writing
.tategaki { writing-mode: vertical-rl; text-orientation: mixed; font-family: 'Yu Mincho', serif; }
CJK font optimization
- Korean: Pretendard (7MB → 100KB subset).
- Japanese: Noto Sans JP, Yu Gothic.
- Chinese (SC/TC): Noto Sans SC/TC.
- Subsetting is mandatory.
RTL
<html lang="ar" dir="rtl">...</html>
Use logical CSS properties (margin-inline-start, padding-inline-end) — auto LTR/RTL.
13. Checklist + anti-patterns
Pre-launch a11y checklist (15)
- Meaningful
alton every image (decorative:alt=""). - All inputs labeled.
- Contrast ≥ 4.5:1.
- One
<h1>per page; no skipped heading levels. - Full keyboard operability.
- Visible
:focus-visible. - Touch targets ≥ 24×24.
- Modal focus trap + Esc close.
- Skip link.
prefers-reduced-motionhonored.- Live regions for dynamic content.
- Lighthouse a11y score 100.
- axe-core / Pa11y in CI.
- Manual SR testing.
- Real users with disabilities.
Top 10 a11y / i18n anti-patterns
<div onclick>sprawl.outline: nonewithout:focus-visible.- Placeholder instead of label.
alt="image of ..."(redundant).aria-hiddenon focusable.- Color-only info.
- Autoplay audio/video.
- String concat
"Hi " + name(particles, word order). - Manual date/currency formatting (use Intl).
- Bulk Google Translate without review.
Next episode
Season 6 Episode 8: Frontend Monitoring & Error Tracking 2025 — Sentry, Datadog RUM, PostHog, LogRocket, Session Replay, Source Maps, AI anomaly detection.
"Accessibility isn't a favor for a minority. It's quality for everyone. i18n isn't translation — it's respect for people with different rhythms of life."
— End of Accessibility & Internationalization.