Accessibility
Multilingual shouldn't mean less accessible. PerfLocale aligns with WCAG 2.1 AA across every surface it renders - language switcher, admin pages, SEO markup, View Transitions, and RTL support - and ships an automated axe-core audit suite as a regression guard.
Keyboard-Navigable Language Switcher
Every switcher variant - block, shortcode, nav menu, and admin bar - is fully keyboard navigable. The dropdown variant implements the WAI-ARIA listbox pattern:
Enter/Spaceopens the panel from the trigger buttonArrowDown/ArrowUpmoves focus between languagesHome/Endjumps to the first or last languageEscapecloses the panel and returns focus to the triggerTabmoves focus out of the listbox without committing a selection
Focus is managed programmatically on open/close so the next keystroke goes to the right element. Click-outside closes the panel cleanly. Tested across all 3 switcher modes - inline, simple list, and dropdown.
ARIA Markup & Landmarks
The switcher emits a proper <nav aria-label="Language switcher"> landmark so screen-reader users can jump straight to it with landmark navigation (VoiceOver rotor, NVDA/JAWS landmarks list). Current-language links carry aria-current="true" so assistive tech announces which language is active. Inside the dropdown:
role="listbox"on the panel,role="option"on each languagearia-haspopup="listbox",aria-expanded,aria-controlson the trigger buttonaria-selectedon the current-language option- Untranslated-language options get
tabindex="-1"(removed from tab order) rather than being hidden - they remain announceable but unselectable, matching the ARIA listbox disabled-option convention
Every ARIA string flows through WordPress i18n (esc_attr__ + translators comments) so assistive tech announces the switcher in the visitor's language, not yours.
<html lang> & Content-Language
Screen readers change pronunciation based on the page's declared language. PerfLocale sets <html lang> to the current language's BCP-47 tag on every frontend page (e.g. lang="de-DE", lang="ar-SA"). JAWS, NVDA, VoiceOver, Orca, and TalkBack all use this attribute to switch voice profile.
On top of that, the plugin emits the W3C-standard Content-Language HTTP response header on every request, also BCP-47 normalised. This is a second signal honoured by proxies, CDNs, and some assistive tech. Turn it off if your CDN mangles the header - filter perflocale/content_language/value customises the value, removing the filter entirely disables the header.
Right-to-Left (RTL) Language Support
Arabic, Hebrew, Persian, Urdu, and other RTL languages are first-class. Every language row in the Languages settings page has a text_direction field (ltr or rtl); when a visitor lands on an RTL language's URL prefix, the plugin filters WordPress's locale to the RTL locale - from there, WP core applies all its standard RTL behaviour automatically, including:
<html dir="rtl">emitted bylanguage_attributes()- Automatic loading of the theme's RTL stylesheet (
style-rtl.css) if present - Core + admin stylesheets flipped to RTL variants
On top of that, the plugin emits the correct BCP-47 language code in its own hreflang + Content-Language output (e.g. ar-SA, he-IL). Integrators can check perflocale()->is_rtl() in templates to branch layout for mixed-direction content (e.g. an English blog with Arabic comments). The axe-core audit suite tests the /ar/ URL explicitly so any regression in RTL rendering breaks the release.
Respects prefers-reduced-motion
When View Transitions are enabled, the 240ms cross-document crossfade is automatically suppressed for users with the OS-level reduced-motion preference. The emitted CSS includes a @media (prefers-reduced-motion: reduce) block that zeros the animation duration - navigation still completes instantly (users still get the instant feel of the speculation-rules prerender), but the crossfade itself is skipped.
This aligns with WCAG 2.1 SC 2.3.3 Animation from Interactions (AAA), protecting users with vestibular disorders or motion sensitivity. Users don't need to configure anything on the WordPress side - the browser already knows their preference from the OS and the CSS reads it automatically.
If you customise the View Transitions CSS via the perflocale/view_transitions/css filter, keep the reduced-motion guard in your replacement block to preserve compliance.
Hreflang for Assistive Tech Discoverability
PerfLocale emits <link rel="alternate" hreflang="..."> tags for every available translation of the current page, plus hreflang="x-default". While hreflang is primarily a search-engine signal, it's also consumed by browser extensions, reader modes (Firefox Reader, Safari Reader), and some screen readers to offer the visitor their preferred-language version if one exists. The plugin's HreflangCoordinator also suppresses duplicate hreflang emission when an SEO plugin (Yoast, Rank Math, AIOSEO, SEOPress) is active, so assistive tech isn't confused by conflicting alternates.
Automated axe-core Audits as a Release Gate
Every release is gated on axe-core audits of the plugin's rendered output. The tests/a11y-e2e.sh suite runs axe-core via Puppeteer against four pages on every site:
- The homepage (default LTR language)
- A
/de/page (LTR non-default - catches regressions tied to URL-prefix routing) - A
/ar/page (RTL language - catches layout/direction regressions) - A switcher-focused test page (exercises dropdown + block markup together)
Violations are filtered to those rooted at PerfLocale-emitted markup (theme issues are reported upstream but don't fail the gate). The plugin must have zero serious or critical violations under WCAG 2 A, AA, WCAG 2.1 AA, and best-practice rule packs - any regression blocks release. The suite runs on all 3 test topologies (single-site, multisite subdomain, multisite subdirectory) so routing quirks that might alter markup don't sneak in. Current score: 39 assertions, 0 failures across the 3 topologies.
Accessible Admin UI
The plugin's own admin pages - Languages, Translations, Strings, Assignments, Settings, the post-edit sidebar - build on WordPress core's accessibility primitives: explicit <label for> pairings on form fields, focus styles inherited from core, screen-reader-text class for visually-hidden labels where visible labels would clutter the layout. Grouped controls (column toggles, workflow metabox rows, import-mode selectors) use <fieldset> + <legend> so screen readers announce the grouping instead of reading each control in isolation.
Every user-facing admin string flows through WordPress i18n (esc_html__, esc_attr__, _e) with translators comments on printf placeholders, so admin UI is localisable and assistive tech announces it in the site-admin's language.
Compliance Posture
What we claim: PerfLocale's rendered markup aligns with WCAG 2.1 Level AA. We ship an automated axe-core regression suite and manually test with NVDA + VoiceOver across each release.
What we don't claim: we can't guarantee your finished site is WCAG compliant - that also depends on your theme, your content, your images' alt text, your colour choices, and any third-party plugins. A multilingual plugin can hand you compliant plumbing (landmarks, language codes, focus management, motion guards); compliance of the rendered page is a site-wide property you verify with your own auditor.
If you find an accessibility regression in PerfLocale's own markup, please report it - it's a release-blocking bug for us.