Missing language declaration
html-has-lang
- WCAG success criterion
- 3.1.1 (level A)
- Outcome class
- Automatic. An automated check that can report a confirmed violation.
- Area
- Accessibility, Language & Clarity
- Default severity
- high
- axe-core rule
document/lang-missing
Why we flag this
Screen readers read aloud with incorrect pronunciation.
Missing lang attribute.
What the rule checks
Scope. Rendered same-origin iframe documents (also in open shadow roots): the frame is not display:none/visibility:hidden, not aria-hidden, not role=none/presentation, larger than 1x1 px.
Expectation. Fails for every applicable frame document whose root html element has no non-empty lang: lang does not inherit across the document boundary, so the embedded content's language is not programmatically determinable although it is part of the page. A frame document with lang passes. Collapsed or hidden frames present nothing and do not apply. Cross-origin frames cannot be read (frame-tested). One finding per frame.
Source. WCAG 2.2 SC 3.1.1 Language of Page; H57; HTML §3.2.6.2 (lang is inherited within a document only)
Detection: page; needs dom, frames, computed_style, geometry.
Failing example
The check reports a violation here. Corpus case structure_ids_frames, element iframe#reviews-frame [frame] html. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Store finder – Acme Hardware</title>
<style>
body { font: 16px/1.5 sans-serif; margin: 0 16px; }
a { display: inline-block; min-width: 44px; min-height: 44px; line-height: 44px; }
input, button { min-width: 44px; min-height: 44px; }
iframe { border: 1px solid #555; }
.collapsed { display: none; }
</style>
</head>
<body>
<!-- Embedded widgets and help texts (#783). -->
<a href="#main" class="skip-link">Skip to content</a>
<header>
<p>Acme Hardware</p>
</header>
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/stores/">Stores</a></li>
</ul>
</nav>
<main id="main">
<h1>Store finder</h1>
<h2>Map</h2>
<!-- Frames without an accessible name: no title at all, and an empty title. -->
<iframe id="map-frame" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>Map</title></head><body><p>Map of Berlin</p></body></html>"></iframe>
<iframe id="weather-frame" title="" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>Weather</title></head><body><p>Sunny, 21 degrees</p></body></html>"></iframe>
<!-- Named by title and by aria-label: fine. The first embedded document
has a default title, which titles the frame document, not this page;
the second embedded document has no lang. -->
<iframe id="hours-frame" title="Opening hours" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>Untitled</title></head><body><p>Open 8 to 18</p></body></html>"></iframe>
<iframe id="reviews-frame" aria-label="Customer reviews" width="300" height="150"
srcdoc="<!DOCTYPE html><html><head><title>Reviews</title></head><body><p>Four out of five stars</p></body></html>"></iframe>
<!-- Presentational, collapsed and aria-hidden frames are not exposed. -->
<iframe id="tracking-frame" role="presentation" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>t</title></head><body></body></html>"></iframe>
<iframe id="collapsed-frame" class="collapsed" width="300" height="150"
srcdoc="<!DOCTYPE html><html><head><title>c</title></head><body><p>Hidden</p></body></html>"></iframe>
<iframe id="hidden-frame" aria-hidden="true" tabindex="-1" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>h</title></head><body></body></html>"></iframe>
<h2>Find a store</h2>
<form action="/stores" method="get">
<!-- The hint id occurs twice and is referenced: ambiguous. -->
<label for="postcode">Postcode</label>
<input id="postcode" name="postcode" type="text" autocomplete="postal-code" aria-describedby="postcode-hint">
<p id="postcode-hint">Five digits, for example 10115.</p>
<p id="postcode-hint">We only use it to sort the list.</p>
<!-- Duplicated but referenced by nothing: no relationship breaks. -->
<p id="promo-text">Free parking at every store.</p>
<p id="promo-text">Gift cards available.</p>
<button type="submit">Search</button>
</form>
<h2>Store card</h2>
<!-- The component reuses the id inside its own shadow root; IDREFs
resolve within their own tree, so neither reference is ambiguous. -->
<store-card></store-card>
<p id="card-note">Prices may vary by store.</p>
<script>
const card = document.querySelector('store-card').attachShadow({ mode: 'open' });
// The card also has an empty heading and an untitled frame inside its
// shadow root.
card.innerHTML = '<h3 id="card-heading"></h3><p id="card-note">Mitte branch</p><p aria-describedby="card-note">Open Sundays</p>' +
'<iframe id="card-frame" width="300" height="150" srcdoc="<!DOCTYPE html><html><head><title>Card</title></head><body><p>Route</p></body></html>"></iframe>';
</script>
<p aria-describedby="card-note">All stores</p>
</main>
<footer>
<p>Acme Hardware, Berlin</p>
</footer>
</body>
</html>Passing example
The check passes here and reports nothing. Corpus case structure_bypass_missing. The page may contain further findings for other rules.
<!DOCTYPE html>
<html lang="en_US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font: 16px/1.5 sans-serif; margin: 0 16px; }
a { display: inline-block; min-width: 44px; min-height: 44px; line-height: 44px; }
input, button { min-width: 44px; min-height: 44px; }
</style>
</head>
<body>
<!-- An old intranet page (#783): no <title> at all, a locale-style lang
value ("en_US" uses an underscore, which BCP 47 does not allow, so
the primary subtag is not "en"), and a repeated navigation block with
no way to bypass it: no heading, no main landmark, no skip link. -->
<nav>
<a href="/">Intranet home</a>
<a href="/staff/">Staff directory</a>
<a href="/forms/">Request forms</a>
<a href="/help/">IT help desk</a>
</nav>
<!-- The only heading is hidden, so it bypasses nothing. -->
<h1 hidden>Intranet</h1>
<div class="content">
<p>Holiday requests for next year are due by the end of the month.</p>
<p>Use the staff portal to submit them.</p>
<!-- An embedded calendar with its own title: it titles the frame
document, not this page. -->
<iframe title="Staff calendar" width="300" height="150"
srcdoc="<!DOCTYPE html><html lang='en'><head><title>Staff calendar</title></head><body><p>No events today</p></body></html>"></iframe>
</div>
</body>
</html>This page is generated from the rule catalogue and the rule contracts; see therule overview for how the outcome classes are derived.