<html lang="hr"> is four characters of information that a screen reader, a hyphenation engine, a spell checker and a font selector all read before anything else on the page. Leave it out and Croatian text gets read with English pronunciation rules, which makes it noise. Get the value wrong (en_US, a country code, the word "english") and the effect is the same. This guide covers the right value, when to mark up a phrase in a second language, and how lang relates to the two other language signals people confuse it with, hreflang and Content-Language.
Quick answer
- Put
langon the<html>element of every page, with a BCP 47 tag:hr,en,en-GB,de-AT,pt-BR,zh-Hant. - Language first, region optional, hyphen between:
en-GBis right,en_GB,englishandGBare not. - Mark passages in another language with
langon the element:<blockquote lang="fr">. Names, brand names and loan words do not need it. hreflangandContent-Languagedo different jobs (which page version to show, and the intended audience). Neither replaceslang.- Right-to-left languages also need
dir="rtl"on<html>. - In WordPress the theme prints it from Settings → General → Site Language. If it is missing, the theme's template dropped
language_attributes(). - Run the accessibility checker; the language finding shows the value the browser rendered.
Why the language attribute matters
A screen reader is a text-to-speech engine with a set of pronunciation rules per language. It reads lang on <html>, picks the matching voice if one is installed, and applies that language's rules to every word. With lang="en" on a Croatian page, "Košarica" is pronounced as if it were an English word; with no lang, the reader falls back to its default, which is usually the operating system language, and the result is the same for anyone whose OS is not set to Croatian. WCAG 2.2 makes this a level A requirement, 3.1.1 Language of Page, the lowest bar there is.
Screen readers are the loudest beneficiary but not the only one:
- Hyphenation:
hyphens: autoin CSS does nothing until the browser knows the language, because the break rules differ. - Quotation marks:
<q>renders "…" in English, „…" in German and Croatian, « … » in French, driven bylang. - Fonts: Chinese, Japanese and Korean share code points;
lang="ja"versuslang="zh"decides which glyph shapes the browser chooses. - Spell checking and autocorrect in text fields follow the field's or page's language.
- Translation tools use it as one signal alongside the text.
Google Search does not use it. Google's documentation on multi-regional sites says it determines a page's language from the visible content and ignores code-level information such as lang. So the attribute will not fix a page ranking in the wrong country; that is what hreflang is for, and it is the reason the two are covered in one guide: they are different jobs.
How getReport checks it
The page is rendered in Chromium and the lang attribute is read from the live document, so a value set by JavaScript counts and a value the theme prints is what you see. axe-core's html-has-lang and html-lang-valid rules run on the same render, and either one failing turns the finding into a warning:

The evidence line quotes the rendered value or "(missing)". The check does not guess the language of the text and compare it with the attribute; a page in Croatian with lang="en" passes here, so read the value, not just the status. Invalid values such as en_US are caught by axe's validity rule and appear with the rule id in the technical detail.
The same attribute, checked by the SEO module on the raw HTML before rendering. This one applies a loose shape test: two or three letters, then optional subtags separated by hyphens. hr, en-GB and sr-Latn-RS pass; en_US, english and an empty value warn. If the two findings disagree, JavaScript is changing the attribute after load (a translation plugin, usually).
Not the same thing, and worth seeing next to the others. hreflang is a set of <link rel="alternate"> tags naming the other language versions of the page for search engines. It uses the same BCP 47 codes, which is where the confusion starts, but it says nothing about the language of the text on this page. This finding checks the codes are valid, x-default appears at most once and the page lists itself; the x-default guide explains the set.
Step by step
1. Choose the tag
The tag is the language code from ISO 639, in lower case, optionally followed by a script and a region:
| Page language | Tag | Note |
|---|---|---|
| Croatian | hr | The region (hr-HR) adds nothing here |
| English, no particular variety | en | Fine for most sites |
| British English | en-GB | Affects spelling suggestions and some voices |
| Austrian German | de-AT | |
| Brazilian Portuguese | pt-BR | Voices differ noticeably from pt-PT |
| Traditional Chinese | zh-Hant | Script subtag; zh-TW also works |
| Serbian in Latin script | sr-Latn | |
| Ukrainian | uk | Not the United Kingdom |
Three rules cover almost every mistake: the language comes first and is required; the separator is a hyphen; the region alone is not a language (uk is Ukrainian, GB is nothing). Case does not matter to browsers, but the convention is lower-case language and upper-case region.
2. Put it on the html element
<!doctype html>
<html lang="hr">
<head>
<meta charset="utf-8">
…Once, on <html>, on every page. Not on <body> and not only on the content div: the <title> and everything in <head> would then have no language.
For right-to-left languages, add the direction at the same place:
<html lang="ar" dir="rtl">dir is not implied by lang; without it the text is right-to-left but the layout, punctuation placement and scrollbars are not.
3. Mark up parts in another language
WCAG 3.1.2 Language of Parts, level AA, asks for the language of any passage that differs from the page. The screen reader switches voice for that element and back afterwards:
<p>The review in <em>Le Monde</em> called it
<q lang="fr">un petit chef-d'œuvre de précision</q>, which is generous.</p>
<blockquote lang="de">
<p>Wer nicht fragt, bleibt dumm.</p>
</blockquote>
<a href="/de/" lang="de" hreflang="de">Deutsch</a>The language switcher is the case that matters most: a link reading "Deutsch" on an English page should be pronounced in German, or a German-speaking visitor may not recognise it. Put lang on each switcher link.
What does not need marking, per the criterion: proper names (Le Monde, Škoda), technical terms, words that have become part of the page's language (rendezvous, kindergarten, pizza) and text whose language cannot be determined. Marking every borrowed word makes the reading choppier, not clearer.
4. Language in forms and user content
Text fields inherit the page language for spell checking. On a page where visitors type in another language (a translation form, a bilingual contact form), set lang on the field:
<label for="msg-en">Your message in English</label>
<textarea id="msg-en" lang="en"></textarea>For content whose language you cannot know in advance (comments, reviews), dir="auto" at least gets the direction right from the first strong character; the language has to stay the page's.
5. Keep hreflang and Content-Language in their lanes
Three signals, three jobs:
| Signal | Where | Who reads it | Says |
|---|---|---|---|
lang attribute | <html> and elements | Browsers, screen readers, CSS | The language of this text |
hreflang | <link rel="alternate"> or the sitemap | Search engines | The other language versions of this URL |
Content-Language header | HTTP response | Almost nobody today | The audience the page is intended for |
A page can legitimately have lang="en", hreflang links to de and fr versions and no Content-Language at all. If your server or CMS sends Content-Language, keep it consistent with lang; if it does not, do not add it for this purpose. The <meta http-equiv="Content-Language"> form is obsolete and is not a substitute for the attribute.
6. Re-run
The accessibility finding should read "The page declares its language" with the value quoted, and the SEO finding should pass. If either still warns, the value is being changed after load: step 2 of Platform notes covers the WordPress case.
Platform notes
WordPress
WordPress prints lang from Settings → General → Site Language. The theme's template calls language_attributes(), which outputs lang="hr" (or lang="en-US" for the default English install) and dir="rtl" for right-to-left locales. Block themes do this in core's template canvas, so a block theme always has it. Three ways it goes missing or wrong on classic themes:
- Hard-coded:
header.phpreads<html lang="en">because the theme author typed it. Replace it with<html <?php language_attributes(); ?>>in the child theme'sheader.php. - Site Language set to English on a Croatian site: the attribute is present and wrong. Change the setting; it also switches the admin and the theme's strings, so check the front end afterwards.
- Multilingual plugins: WPML and Polylang set the attribute per language automatically and add the
hreflanglinks; a page in the wrong language after switching means the plugin's language for that page is set wrong, not the theme.
Page builders (Elementor, Divi) replace the visible header, not the <html> element, so the attribute still comes from the theme. Hello Elementor and other builder base themes use language_attributes().
For a phrase in another language inside a post, the block editor has no language control on inline text; edit the block as HTML (block menu → Edit as HTML) and add lang to a <span> or the block's element.
Shopify
The theme's theme.liquid prints <html lang="{{ request.locale.iso_code }}">, which follows the store's languages under Settings → Languages. Markets with multiple languages get the right code per URL automatically.
Static sites and custom builds
Set it in the layout template from the page's front matter or the build's locale. Frameworks with i18n routing (Next.js, Nuxt, Astro, Hugo) expose the current locale to the layout; the only mistake to watch for is a template that hard-codes en for all locales.
Verify
- The accessibility checker's language finding passes and quotes the tag you set; the SEO audit's html lang finding passes.
- In the browser console,
document.documentElement.langreturns the tag (this is the value screen readers use). - With a screen reader (VoiceOver on a Mac: Cmd+F5), read the first heading; the voice and pronunciation match the language. On a page with a
lang="fr"quote, the voice switches for the quote and back. - On a multilingual site, open each language version: the tag changes with the URL.
Common mistakes
- Underscore instead of hyphen:
en_UScomes from operating system locales and fails validation in browsers and in the report. Useen-US. - Country instead of language:
lang="uk"on a British site announces Ukrainian;lang="jp"is not Japanese (that isja). - Language name instead of code:
lang="english"orlang="hrvatski". Codes only. - Right language, wrong page: a multilingual site where every version prints the default language's tag because the theme hard-codes it.
langon the content div only: the title, navigation and footer are still read in the wrong language. Put it on<html>.- Marking every foreign word: a menu of Italian dish names does not need
lang="it"on each one; names are exempt and the constant voice switching is worse than the accent.