The HTML lang attribute tells browsers and assistive technology which language a page uses. Put it on the root html element, choose a language tag that matches the page, and verify the value in the deployed HTML. This small declaration helps screen readers apply the right pronunciation rules and gives translation and text-processing tools useful context.
This guide covers the page-level declaration, language changes inside a page, framework pitfalls, and a production test you can keep in CI. It also separates lang from hreflang: they solve different problems.
HTML lang attribute checklist
Use this sequence before release:
- Set a non-empty
langvalue on the roothtmlelement. - Match the value to the page's predominant human language.
- Use a valid BCP 47 language tag, such as
en,en-GB,fr, ores-419. - Add
langto passages whose language differs from the page default. - Update the root value when a client-side locale switch changes the page language.
- Check the initial production response and the rendered DOM.
- Test representative routes for every supported locale.
- Keep
lang,hreflang, and locale URLs consistent without treating them as interchangeable.
Do not copy en-US into every template because it looks familiar. The correct value describes the content a visitor receives at that URL.
Add the HTML lang attribute to the document root
For an English page, the minimum useful declaration is:
<!doctype html>
<html lang="en">
<head>
<title>Release notes</title>
</head>
<body>
<h1>Release notes</h1>
</body>
</html>
The lang attribute is global, but the root declaration establishes the default language inherited by the page. MDN's `lang` reference explains that the value is a BCP 47 language tag and that descendants inherit a language when they do not declare their own.
WCAG 2.2 Success Criterion 3.1.1 requires the default human language of a page to be programmatically determinable. The W3C's Language of Page guidance describes why this matters: assistive technologies and other user agents can apply appropriate pronunciation and presentation rules.
Add the attribute in the template that owns the document element. A component nested inside body cannot repair a missing root declaration by wrapping the application in <div lang="en">. That may identify the component's language, but the document element still has no page-level value.
Choose a BCP 47 language tag
Start with the shortest tag that accurately distinguishes the content:
enfor English when a regional distinction is unnecessary;en-GBfor content intentionally written as British English;fr-CAfor Canadian French;es-419for Latin American Spanish; andzh-Hantwhen the Traditional Chinese script distinction is useful.
BCP 47 tags can include language, script, region, and variant subtags. More detail is not automatically better. The W3C guide to choosing a language tag recommends adding script or region information only when it makes a distinction the application needs.
Avoid values such as english, UK, or an internal locale name like en_US. Use a hyphen, not an underscore, between subtags. If your product stores locales in another format, map them at the document boundary:
const htmlLanguageByLocale: Record<string, string> = {
en_US: "en-US",
en_GB: "en-GB",
fr_CA: "fr-CA",
};
document.documentElement.lang = htmlLanguageByLocale[activeLocale];
Keep the mapping reviewed rather than transforming arbitrary strings at runtime. A syntactically plausible value can still describe the wrong language.
Mark language changes inside the page
The root HTML lang attribute supplies the default. When a passage uses another language, declare that change on the smallest useful element:
<html lang="en">
<body>
<p>The French release is <span lang="fr">prêt à publier</span>.</p>
<blockquote lang="es">
El informe está listo para revisar.
</blockquote>
</body>
</html>
WCAG Success Criterion 3.1.2 covers the language of parts. Correctly marked passages can let supporting screen readers switch pronunciation rules. Proper names, technical terms, and words that have become part of the surrounding language have defined exceptions, so do not add a language wrapper to every borrowed word without considering the sentence.
Test accessible names as well as visible paragraphs. A button label or link name in another language may be announced as a flat string, and support for language changes inside an accessible name can vary. The accessible name guide explains how visible labels, ARIA labels, and computed names interact.
Update lang when the locale changes
Server-rendered routes should send the correct root value in their initial HTML. That gives the browser and assistive technology the right default before client code runs.
If a single-page application changes the complete interface language without a navigation, update document.documentElement.lang as part of the same locale change. Then verify that the page title, visible content, accessible names, and error messages changed too. Updating only the root attribute creates accurate metadata for the wrong interface; translating the interface without updating lang leaves the old pronunciation context in place.
For applications with one URL per locale, test the response for each route:
/ -> <html lang="en">
/fr/ -> <html lang="fr">
/pt-br/ -> <html lang="pt-BR">
Make the route-to-language contract explicit. A default fallback can hide a missing translation by returning English content under a French URL with lang="fr".
Keep HTML lang and hreflang separate
The HTML lang attribute describes the language of the current document. hreflang links identify alternate localized URLs. A multilingual page can need both, but one does not replace the other.
Google Search says it detects a page's language from its textual content, not from code-level language attributes. Do not promise an indexing or ranking change from adding lang. Use it because it is part of the page's accessibility and language-processing contract.
When separate localized URLs exist, follow Google's localized-version guidance for reciprocal hreflang annotations. Keep those URLs aligned with canonicals and sitemaps. The canonical URL and redirects guide covers the preferred-URL side of that release check.
Test the HTML lang attribute in production
Inspect the response that a browser receives, not only the source template in the repository:
curl -sS https://example.com/ | grep -i -m1 '<html'
curl -sS https://example.com/fr/ | grep -i -m1 '<html'
Then check the rendered value in DevTools:
document.documentElement.lang
The response and DOM can disagree when a framework injects the locale after hydration. Record both values if that happens. Fix the server output first when the route language is known at request or build time, then keep the client update for in-place locale changes.
Check more than presence:
- The value is non-empty.
- The tag is well formed and uses the intended subtags.
- The declared language matches the predominant visible content.
- Localized routes do not fall back to the default language silently.
- Passages in another language carry their own
langwhere needed. - The value remains correct after client-side navigation or locale switching.
Add the result to the broader accessibility checks before launch, which covers headings, labels, keyboard flow, contrast, and narrow layouts.
Add an HTML lang attribute check to Playwright
For a site with known locale routes, exact expected values produce clearer failures than a loose pattern:
import { expect, test } from "@playwright/test";
const localeRoutes = [
{ path: "/", language: "en" },
{ path: "/fr/", language: "fr" },
{ path: "/pt-br/", language: "pt-BR" },
];
for (const locale of localeRoutes) {
test(`${locale.path} declares ${locale.language}`, async ({ page }) => {
await page.goto(`https://example.com${locale.path}`);
await expect(page.locator("html")).toHaveAttribute(
"lang",
locale.language,
);
});
}
Add a separate assertion for representative visible text so a route cannot pass by changing the attribute alone. If the product supports an in-page locale switch, exercise it and assert the root attribute, heading, and primary action after the switch.
Put stable route expectations in CI, then repeat the deployed check after release. The website audit with GitHub Actions guide shows how to protect tokens and keep public-URL evidence reviewable. The website regression testing checklist covers baselines and post-deployment comparisons.
Check the HTML lang attribute with Slop Audit
Run a Slop Audit on the deployed page after publishing it. Slop Audit inspects the initial public HTML for a non-empty lang attribute on the root html element and records the observed value as evidence. That turns a missing declaration into a specific release finding tied to the tested URL.
The check is intentionally bounded. It does not validate every BCP 47 subtag, prove that the value matches the visible language, inspect every language change inside the page, test a locale switch, configure hreflang, run a screen reader, or certify WCAG conformance. Use the scan for deployed evidence, then keep the exact route assertions and manual language review in your release process.
HTML lang attribute release sign-off
- Every public HTML page has a non-empty root
langvalue. - The value is a suitable BCP 47 tag for the page's predominant language.
- Localized routes return the expected value in initial HTML.
- Client-side locale changes update the document value and the interface together.
- Passages in another language are marked where the change matters.
hreflang, canonicals, and localized URLs are tested as separate search controls.- CI checks stable locale routes, and the deployed public URL is checked again after release.
The HTML lang attribute is a small declaration with a precise job. Set it where the document begins, make it match the content, and keep the production evidence with the release.