( Understandable / WCAG 3.1.2 )

lang attribute on an element has an invalid value

SeriousLevel AAWCAG 3.1.2 — Language of Parts

What is this issue?

An element inside the page body (commonly a <span>, <p>, <blockquote>, or similar) carries its own lang attribute, used to mark that one passage as being in a different language than the rest of the page. The value on that attribute isn’t a valid BCP 47 language tag: a typo, a made-up code, or a value that doesn’t map to any recognized language.

This is the element-level counterpart to html-lang-valid, which checks the same kind of invalid value but on the <html> element’s page-wide default. Here, the scope is narrower: one passage inside an otherwise-correctly-declared page.

Why does this matter?

The lang attribute on an inline element exists specifically so a screen reader can switch pronunciation rules mid-page: read the surrounding text in the page’s default language, then briefly switch voice and pronunciation rules for a quoted foreign phrase, then switch back. When that attribute’s value is invalid, the screen reader has no valid target to switch to, so it keeps reading the passage with whatever pronunciation rules it was already using, mangling the foreign phrase instead of pronouncing it correctly.

This matters most for content that specifically depends on correct pronunciation to make sense: a menu listing dishes in their original language, a quoted phrase in legal or academic writing, a person’s name cited alongside its native-language spelling. Getting the surrounding page’s language right and still failing on these individual passages leaves exactly the content that needed the most careful pronunciation handling unhandled.

Who is affected?

  • Screen reader users: hear a foreign-language passage read with the wrong pronunciation rules, since the invalid lang value fails to trigger the intended voice switch, producing a mispronounced phrase in the middle of an otherwise correctly-read page.

What users experience

Tom uses VoiceOver on his iPhone to browse a restaurant’s online menu. The English-language page correctly declares lang="en" on <html>, but the French dish names are marked up as <span lang="frn">crème brûlée</span>, a typo that isn’t a valid BCP 47 code. VoiceOver can’t resolve "frn" to French, so it reads the dish name using its default English pronunciation rules, turning “crème brûlée” into a garbled string of sounds instead of the recognizable dish name a sighted menu reader would see printed correctly on the page.

How do I fix it?

Correct the invalid value to a valid BCP 47 code matching the passage’s actual language: most commonly the same two-letter ISO 639-1 code used for a page-level lang attribute, such as fr for French or ja for Japanese. This works the same way page-level lang correction does: a valid code gives the screen reader an exact target to switch its pronunciation engine to for the duration of that element.

Search the page for every element carrying a lang attribute, not just the one that triggered the report, since this defect commonly recurs across multiple foreign-language passages that were all typed by hand from the same source rather than generated from a validated list of language codes.

Code Examples

Before
<p>The menu features <span lang="frn">crème brûlée</span> for dessert.</p>
After
<p>The menu features <span lang="fr">crème brûlée</span> for dessert.</p>

The correction is a single character change here ("frn" to "fr"), but the effect is significant: with a valid code, a screen reader now switches to genuine French pronunciation rules for the wrapped phrase, then reverts to the page’s default English rules the moment it exits the <span>.

Common Mistakes

Mistake: “This attribute is just for styling or search engines, so a typo in it doesn’t really matter.” The lang attribute on an inline element has no visual effect at all; its only consumers are assistive technology pronunciation engines and, to a lesser extent, spell-checkers and search indexing. A typo here is invisible to anyone testing the page visually, which is exactly why it tends to survive unnoticed.

Mistake: “We already fixed the page-level lang attribute, so language handling is done.” A correct page-level lang on <html> only sets the default for content that doesn’t override it. Every element with its own lang attribute needs its own valid value checked independently: fixing the page default doesn’t touch any of them.

Mistake: “Loanwords that English speakers use every day, like ‘café’ or ‘résumé’, need a lang attribute too.” WCAG 3.1.2 is about genuinely foreign-language passages, not words that have been absorbed into the page’s primary language through common usage. Wrapping every accented loanword in its own lang span adds noise without meaningfully helping pronunciation, since the word is already read correctly by the page’s default language rules.

How RedFlag Detects This

Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s valid-lang rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule scans every element on the page (excluding <html>, which html-lang-valid covers separately) with a lang or xml:lang attribute, and checks the value against axe-core’s list of valid BCP 47 primary language subtags.

False negative: axe-core confirms the value is a syntactically valid language subtag; it cannot judge whether that language is the correct one for the wrapped passage: a French phrase incorrectly marked lang="es" is still a valid code, so it passes this specific check even though it names the wrong language for that content. False positive: none typical for this check, since matching against a fixed list of valid subtags is a binary condition axe-core evaluates reliably. Manual step: for every element carrying a lang attribute, confirm the declared language actually matches that specific passage’s content, not just that the code is syntactically real.

Manual Testing

  1. Search the page’s rendered HTML (browser developer tools, “View Page Source,” or an accessibility inspector extension) for every element carrying a lang attribute other than <html>.
  2. Compare each value against the IANA Language Subtag Registry or a known list of valid BCP 47 codes.
  3. Load the page with NVDA or VoiceOver running and listen specifically to how each flagged passage is pronounced.
  4. If a passage is read with the page’s default pronunciation instead of switching voice or accent, or the lang value doesn’t match any real language code, the check fails for that element.

3.1.2 Language of Parts: The human language of each passage or phrase in the content must be programmatically determinable, when it differs from the page’s default language. An invalid lang value on such a passage fails this criterion directly, since the language of that specific part can’t be programmatically determined any better than if no lang attribute were present at all.

lang attribute has an invalid value applies the identical validity check to the <html> element’s page-wide default instead of an individual passage: the same defect, one level up in scope.

Page language is not set covers the page-level default having no lang attribute at all, the base case this rule’s page-wide sibling builds on.

lang and xml:lang do not match covers a different page-level language conflict: two declarations disagreeing with each other, rather than one invalid value on a single element.

HTML lang attribute is not a valid BCP 47 tag documents a related concept but has no automated detector of its own; this rule, valid-lang, is the actual automated check worth relying on for an invalid language code, whether at the page level or on an individual element.

References

Frequently asked questions

Does every foreign word or phrase need its own lang attribute?

WCAG 3.1.2 applies to phrases that are genuinely part of a different language, such as a quoted foreign phrase or a proper noun cited in its original language, not to loanwords that have become part of everyday usage in the page's primary language, like "cafe" or "resume" in English text.

Is this the same rule as html-lang-valid?

No. html-lang-valid checks the lang attribute on the html element, which sets the page's default language. This rule, valid-lang, checks the lang attribute on every other element, catching an invalid code on one specific passage rather than the page-wide default: the two rules apply the identical validity test at two different scopes.

Does the lang attribute on a passage override the page-level lang entirely?

Only for the content inside that element. A span with lang="fr" wrapping one sentence tells the screen reader to switch pronunciation rules just for that sentence, then switch back to the page-level default the moment it exits that element; it does not change the language for the rest of the page.

Can I use a three-letter language code instead of two letters?

Only when no two-letter ISO 639-1 code exists for that language. BCP 47 requires using the shortest valid code available, so a language with a two-letter code, like French (fr), should never use its three-letter equivalent (fra) instead; three-letter codes are reserved for languages that have no two-letter option.

Does this apply to lang attributes inside SVG or other embedded content?

Yes, if the element supports a lang attribute and is part of the rendered page's accessibility tree, the same validity requirement applies regardless of whether the element is plain HTML, inline SVG, or another embedded format.