( Understandable / WCAG 3.1.1 )
Page language is not set
What is this issue?
The <html> element has no lang attribute at all, or the attribute exists but its value is empty after trimming whitespace. There is no declared language anywhere in the document for the browser or assistive technology to read.
This is a page-level omission: it affects every piece of text on the page, not one specific element, because lang on <html> sets the default language every other part of the document inherits unless it explicitly overrides it with its own lang attribute.
Why does this matter?
Screen readers: software that reads a page’s content and structure out loud (or to a braille display) for people who are blind or have low vision use lang to select which speech-synthesis voice and pronunciation rules to apply. Without it, most screen readers fall back to whatever language their user has set as their system default, which may or may not match the page’s actual language.
When that fallback guesses wrong, the mismatch isn’t a minor accent quirk; it can make the entire page unintelligible. A French-language voice trying to read English word-by-word using French pronunciation rules produces speech a native English listener can’t follow at all, and the reverse is just as broken. This isn’t a cosmetic annoyance; it can make an entire page functionally unusable for anyone relying on synthesized speech.
Who is affected?
- Screen reader users: hear the page read in the wrong voice and pronunciation entirely, which can range from an odd accent to completely unintelligible speech depending on how different the fallback language is from the page’s actual language.
- Cognitive disabilities: users who rely on clear, correctly-paced speech output to process written content lose comprehension entirely when the pronunciation engine is wrong for the language being read, adding a barrier on top of whatever the page’s content already required.
What users experience
Hiroshi uses VoiceOver on his Mac, with his system language set to Japanese. He opens an English-language product page that has no lang attribute on <html>. VoiceOver falls back to reading the page with Japanese pronunciation rules applied to English text, producing speech Hiroshi can’t parse as words at all, closer to disconnected sounds than sentences. He closes the tab immediately, since there’s no quick way to know the page even was in English rather than being broken content.
How do I fix it?
Add a lang attribute to the <html> element, set to the page’s primary language using a BCP 47 language tag, most commonly a two-letter ISO 639-1 code like en, fr, or es. This works because lang on <html> is the single default every screen reader checks first, so setting it correctly fixes pronunciation for the entire page in one change.
If the page needs to signal a specific regional variant (American versus British English spelling and date conventions, for instance), add a region subtag, like en-US or en-GB. For a multi-language site, set lang dynamically to match whichever language the current page is actually rendered in, rather than hardcoding one value across every template.
Code Examples
<html>
<head>...</head>
<body>...</body>
</html><!-- Generic English -->
<html lang="en">
<head>...</head>
<body>...</body>
</html>
<!-- With a region subtag, when it matters -->
<html lang="en-GB">
<head>...</head>
<body>...</body>
</html>Both versions fix the same underlying problem: a screen reader now has an explicit language to switch its pronunciation engine to, instead of guessing from the user’s own system settings. The region subtag is optional; add it only when the distinction (spelling, date format, terminology) genuinely matters for the content.
Common Mistakes
Mistake: “The page is obviously in English, so a screen reader can figure that out on its own.” A screen reader has no reliable way to detect language from text content alone before reading it; it relies entirely on the declared lang attribute, falling back to the user’s own system language setting when none exists. “Obvious” to a sighted reader who can see the words is invisible to a program that hasn’t started reading yet.
Mistake: “We localize with JavaScript, so lang gets set dynamically and doesn’t need to be in the initial HTML.” If lang is only set after JavaScript runs, a screen reader that starts reading before that script executes (or any tool that reads the initial server-rendered HTML, including search crawlers) still sees a page with no language declared. Set lang in the server-rendered markup whenever possible, not only after client-side hydration.
Mistake: “This only matters for non-English sites.” A missing lang attribute causes a screen reader to fall back to the user’s system language, and that user’s system language is not always English even on a site written entirely in English. The failure mode is the same regardless of which language the page is actually written in; there’s simply no reliable default.
How RedFlag Detects This
Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s html-has-lang rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule checks the <html> element once per scan for a lang (or xml:lang) attribute whose trimmed value is non-empty.
False negative: axe-core confirms a lang value exists; it cannot judge whether that value is the correct language for the page’s actual content: a page in French with lang="en" set passes this specific check even though the declared language is wrong (that mismatch is what a human reviewer needs to catch). False positive: none typical for this check, since the presence of a non-empty lang attribute is a binary condition axe-core evaluates reliably. Manual step: confirm the declared lang value genuinely matches the page’s primary written language, not just that some value is present.
This check is a twin of redflag-lang-missing, an older custom DOM check RedFlag built before adopting axe-core for this rule. Both detect the identical condition (a missing or empty lang attribute on <html>), so when both fire on the same page, RedFlag’s dashboard deduplicates them into a single finding rather than reporting the same defect twice.
Manual Testing
- Open the page in Chrome or Firefox with NVDA or VoiceOver running.
- Load the page fresh and listen to the voice and accent the screen reader uses to read the first paragraph of visible text.
- Open the browser’s developer tools and check the
<html>element’slangattribute directly, or use an extension like the WAVE toolbar, which flags a missinglangexplicitly. - If the announced speech uses an unexpected accent or mispronounces common words badly, or if the
langattribute is absent from<html>in the DOM inspector, the check fails.
Related WCAG Success Criteria
3.1.1 Language of Page: The default human language of each web page must be programmatically determinable. This rule is a direct check of that requirement: a missing lang attribute means the page’s language cannot be determined programmatically at all, which is exactly what 3.1.1 requires to exist.
Related Issues
Page is missing a language declaration is this rule’s detector twin: the identical missing-lang condition, caught by an older custom check instead of axe-core, and deduplicated with this rule on RedFlag’s dashboard when both fire together.
lang attribute has an invalid value is the closely related failure one step past this one: lang is present, but its value isn’t a real language code, which causes the same mispronunciation outcome through a different defect.
lang and xml:lang do not match covers a page that declares a language twice, in two conflicting ways, rather than not declaring one at all.
lang attribute on an element has an invalid value is the same invalid-language-code problem scoped to a single passage inside the page instead of the page-level default this rule covers.
Page is missing a descriptive title shares this rule’s place as page-level metadata a screen reader reads immediately on load, before any visible content; both need to be correct from the very first moment on any page.
References
- W3C Understanding 3.1.1: Language of Page
- W3C Technique H57: Using the language attribute on the html element
- MDN: lang global attribute
- WebAIM: Language of Pages and Parts
Frequently asked questions
What value should the lang attribute have for an English page?
Use "en" for generic English, or a region subtag like "en-US" or "en-GB" if the distinction matters for your content, such as spelling or date formats. The two-letter base code is the part screen readers rely on most for pronunciation.
Does lang need to be on every element, or just html?
The html element sets the default language for the whole page and is the minimum this rule requires. Individual elements only need their own lang attribute when a specific passage switches to a different language than the page default; that is a separate rule, valid-lang.
Why do redflag-lang-missing and html-has-lang both exist for the same defect?
They are two different detectors checking the identical condition: html-has-lang is RedFlag's axe-core check, and redflag-lang-missing is an older custom DOM check RedFlag built before adopting axe-core for this rule. RedFlag treats them as the same finding and shows only one entry on the dashboard when both fire on a page.
Does setting lang on the body element instead of html satisfy this rule?
No. The html-has-lang check looks specifically at the html element, since that is the element the HTML specification defines as setting the document's default language. A lang attribute placed only on body is not read the same way by every browser and screen reader combination.
Is this the same issue as an invalid lang value?
No, they are two separate rules. This rule fires when lang is missing entirely; html-lang-valid fires when lang is present but set to something that is not a real language code, like a typo. Both cause mispronunciation, but the fix and the underlying defect are different.