AccessScanRun a free scan

Guide

The lang attribute and multilingual pages: getting WCAG 3.1.1 and 3.1.2 right

A screen reader decides how to pronounce your page before it reads a single word. It looks at the lang attribute on <html>. Get it wrong and an English page is read with French phonemes, or a German voice mangles your English nav. Get the per-passage tagging wrong and a quoted sentence in another language comes out as gibberish. This is what WCAG 3.1.1 Language of Page (Level A) and 3.1.2 Language of Parts (Level AA) exist to prevent.

For anyone working on a lang attribute multilingual setup, these two criteria are deceptively simple to state and easy to get subtly wrong, especially once a framework, an i18n library, or user-switchable locales enter the picture. This guide covers the exact markup, the valid values, the framework patterns, and the edge cases that automated checkers miss.

What 3.1.1 and 3.1.2 actually require

Two separate success criteria, two scopes:

  • 3.1.1 Language of Page (Level A): the default human language of the whole page must be programmatically determinable. In practice this means a valid lang attribute on the <html> element.
  • 3.1.2 Language of Parts (Level AA): any passage or phrase in a different language from the page default must have its own lang attribute on the wrapping element, unless it's a proper name, a technical term, a word of indeterminate language, or a word that has become part of the surrounding language.

Both sit inside the WCAG 2.2 Level A and AA baseline that the European Accessibility Act enforces from 28 June 2025 via EN 301 549. The same two criteria appear at their respective levels in Section 508 (WCAG 2.0 AA), AODA (WCAG 2.0 AA), and the de-facto ADA standard, so there is no jurisdiction where you can skip them. See our WCAG criteria reference for how they fit the full set.

Setting the page language: html lang done correctly

The value is a BCP 47 language tag, not a country name and not a guess. Use the shortest valid subtag set that's accurate:

  • <html lang="en"> for English where the regional variant doesn't matter.
  • <html lang="en-GB"> or <html lang="pt-BR"> only when the regional distinction is meaningful (spelling, currency phrasing, voice selection).
  • <html lang="zh-Hans"> / <html lang="zh-Hant"> use script subtags for Simplified vs Traditional Chinese, which matters more than region here.

Common mistakes: using lang="english" (invalid, must be the code), using lang="en-US" reflexively when plain en is more honest, or leaving the Create-Next-App-style default lang="en" on a page whose content is actually German. The attribute must match the content, not the template.

If your document is XHTML or served as XML, also set xml:lang with the same value. For ordinary HTML5 served as text/html, the lang attribute alone is correct and sufficient.

Marking foreign-language passages (3.1.2)

Wrap any run of text in a different language and tag it. Inline phrases use <span>; block-level quotes use <blockquote>, <p>, or a list item:

Example: an English page quoting French. <p>She ended with a quiet <span lang="fr">je ne sais quoi</span> and left.</p> Without the span, a screen reader pronounces "je ne sais quoi" using English letter-to-sound rules.

What you do NOT tag: proper names ("Volkswagen" on an English page), single borrowed words that are now part of the language ("rendezvous", "croissant"), technical terms, and text in an undetermined language. Over-tagging is not a failure, but tagging every loanword is noise; reserve it for genuine other-language passages a synthesizer would mispronounce.

For content with no spoken form, or unknown content, lang="zxx" (no linguistic content) and lang="und" (undetermined) are the correct BCP 47 escape hatches rather than a wrong tag.

Multilingual site patterns and framework wiring

The hard part is keeping <html lang> in sync with the rendered locale on every route. Patterns by stack:

  • Static / templated sites: set lang from the build locale so each generated page ships the correct value. A single hardcoded lang in a shared layout is the most common multilingual bug.
  • React SPAs: the root HTML often renders before the router knows the locale. Update it on locale change with document.documentElement.lang = locale inside an effect, so the attribute follows client-side navigation and language switches.
  • Next.js App Router: read the active locale where you render the root <html> element and pass it as the lang prop per request, rather than baking in a constant. Because this Next.js version's conventions differ from older releases, confirm the current API in node_modules/next/dist/docs before wiring it.
  • i18n libraries (i18next, FormatJS, etc.): drive the attribute from the same locale state that drives translations, so there is one source of truth and the two can never disagree.

A language switcher itself needs care: the links to other-language versions should carry hreflang for the target, and if a menu lists language names in their own script ("Deutsch", "日本語", "Español"), tag each item with its own lang so the option names are read correctly.

Testing it (and what automation can and can't catch)

  • Automated: a scanner reliably flags a missing or empty <html lang> and invalid tag syntax. Run a free pass with AccessScan to catch those page-level misses across your routes.
  • What automation can't judge: whether the declared language matches the actual content, or whether an untagged foreign passage should have been tagged. No tool reads meaning, so 3.1.2 needs human review.
  • Manual: turn on a screen reader (VoiceOver, NVDA) and listen. A wrong page language or a missing part tag is immediately audible.

Fold these checks into your release process via the accessibility checklist so locale-correct lang becomes a build expectation rather than an afterthought. The combination of automated page-level scanning and a short manual pass on translated content covers both criteria without much overhead.

Check your site against AccessScan

See your issues ranked by impact in seconds — free.

Run a free accessibility scan

FAQ

Is the lang attribute on <html> required, or just recommended?

Required to meet WCAG 3.1.1 Language of Page at Level A. A valid lang attribute on the html element is the standard way to make the page's default language programmatically determinable, which assistive technology relies on to pick the correct pronunciation.

Do I need lang on every foreign word?

No. 3.1.2 applies to passages and phrases in another language. Proper names, technical terms, words of indeterminate language, and loanwords that have become part of the surrounding language are explicitly exempt. Tag genuine other-language passages a screen reader would otherwise mispronounce.

What value do I use for region or script variants?

Use BCP 47 tags. Add a region subtag only when it's meaningful (en-GB, pt-BR) and a script subtag for cases like zh-Hans versus zh-Hant. When the variant doesn't matter, the shortest accurate tag (such as en) is preferred over a needlessly specific one.

How do I keep lang correct in a single-page app?

Drive document.documentElement.lang from the same locale state that controls your translations, updating it whenever the user switches language or the route changes. A single hardcoded value in the root template is the most common multilingual failure because it never reflects the active locale.

Can an automated checker confirm 3.1.2 compliance?

Only partially. Automation reliably flags a missing, empty, or syntactically invalid html lang attribute. It cannot tell whether the declared language matches the content or whether an untagged passage is actually in another language, so 3.1.2 always needs a short manual review with a screen reader.

More guides