The lang attribute on the <html> element states the language this page is written in. Browsers, screen readers and spell checkers read it. hreflang sits on <link rel="alternate"> tags and points to the same page in other languages, so that a search engine can show each searcher the right URL. A translated page needs both: lang set to its own language, and an hreflang set that lists every version. Neither one replaces the other, and Google says it uses neither to work out what language a page is in.
What the lang attribute does#
MDN describes lang as the attribute that "helps define the language of an element". Its value is a single BCP 47 language tag such as en, nl or pt-BR. Child elements inherit it, so putting it on <html> covers the whole document. An empty value sets the language to unknown, and a tag that is not valid BCP 47 sets it to invalid.
The audience is software that has to render or speak the text. The W3C's explanation of WCAG success criterion 3.1.1, Language of Page, lists what goes right when the language is declared: screen readers load the correct pronunciation rules, visual browsers display characters and scripts correctly, and media players show captions correctly. That criterion is Level A, the lowest WCAG conformance level, which is why accessibility checkers treat a missing lang as a real fault. Deque's axe rule html-has-lang rates its user impact as serious.
Two practical rules come from the W3C Internationalization pages:
- Put
langon<html>, not on<body>. The body does not cover the<title>and other text inside<head>. - Do not declare the page language with
<meta http-equiv="Content-Language">. The W3C advises against it, and says the in-page attribute overrides any language information sent in the HTTP header.
lang also works on smaller elements. A Dutch page that quotes a German customer can wrap the quote in <span lang="de">, and a screen reader switches pronunciation for those words. WCAG's criterion 3.1.2, Language of Parts, asks for exactly this.
What hreflang does#
hreflang is an attribute of links. The HTML standard says it "gives the language of the linked resource" and that it is "purely advisory": a browser must judge a page's language from the page itself once it fetches it, never from the link that led there. Combined with rel="alternate", an hreflang value that differs from the current document's language marks the linked page as a translation.
Search engines are where it does its work. Google uses a set of <link rel="alternate" hreflang="..."> tags to understand that several URLs are versions of one page, and then shows the version that fits the searcher's language and region. Every version carries the full set, including a line for itself, and every pair has to point at each other or Google ignores the tags. Our hreflang guide goes through those rules in detail.
The two side by side#
Valid values, and where they differ#
Both attributes take BCP 47 tags, and the W3C's advice on choosing one is to keep the tag as short as the context allows. Add a region only when it distinguishes something your readers would notice, such as British spelling for a spell checker.
Google's hreflang support is narrower than BCP 47. Its documentation names es-419, Spanish for Latin America, as unsupported, since Google accepts only codes listed in ISO 639-1 and ISO 3166-1 Alpha 2. Our reading is that the numeric region 419 is what rules it out, as ISO 3166-1 Alpha 2 codes are two letters. The W3C uses the same es-419 as a correct example of a BCP 47 region subtag. So lang="es-419" is a perfectly good declaration on a page, while the same page's hreflang entry needs es, or a two-letter region such as es-MX.
Case does not matter to either. By convention the region is written in capitals (en-GB) and a script subtag with a capital first letter (zh-Hant).
WordPress adds one wrinkle. Its locales use an underscore (en_US, nl_NL), which is not valid in a language tag. The function behind language_attributes() takes the language from get_bloginfo('language'), which returns a hyphenated tag: the one the WordPress translation supplies, or the locale with its underscore replaced. A theme that calls it prints lang="en-US" or lang="nl-NL".
Do lang and hreflang have to match?#
They should agree on the language, though they need not be the same string. A WordPress page with lang="en-US" whose own hreflang entry is en is fine: both say English. A German translation that still carries lang="en-US" is the bug to look for.
That bug is common on multilingual WordPress sites for a structural reason. A translation plugin that serves /de/ from the original post renders the page through the same theme, and the theme prints whatever language_attributes() returns for the site's language. Unless the plugin rewrites the attribute on the translated response, the German page goes out declaring itself English, and a screen reader reads German text with English pronunciation rules.
Does Google use lang to detect a page's language?#
No. Google's page on localized versions, last updated on 2026-09-21, says: "Google doesn't use hreflang or the HTML lang attribute to detect the language of a page; instead, we use algorithms to determine the language."
Set lang for the people and software reading the page. An accessibility audit flags a missing one whatever Google does with it.
Both in one head#
A pottery studio publishes in English and translates into Italian and Dutch. This is the start of the Italian version of its workshops page:
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<title>Laboratori di ceramica | Linden Pottery</title>
<link rel="canonical" href="https://www.lindenpottery.example/it/workshops/">
<link rel="alternate" hreflang="en" href="https://www.lindenpottery.example/workshops/">
<link rel="alternate" hreflang="it" href="https://www.lindenpottery.example/it/workshops/">
<link rel="alternate" hreflang="nl" href="https://www.lindenpottery.example/nl/workshops/">
<link rel="alternate" hreflang="x-default" href="https://www.lindenpottery.example/workshops/">
</head>lang="it" tells the browser and assistive technology that this document is Italian. The four alternate lines tell a search engine where the English, Italian and Dutch versions live and which one to show everyone else. The it line and the lang value agree, which is what you check first on any translated page. For right-to-left text such as Arabic or Hebrew, the W3C also asks for the dir attribute.
Both attributes on a language switcher#
A language switcher is the one place where hreflang and lang belong on the same element. Each link leads to a page in another language, which is what hreflang on an <a> describes. Its text is usually that language's own name, "Italiano" or "Nederlands", which is text in another language than the page, so lang applies to it.
The W3C warns about one case. If the link also has a title attribute written in the current page's language, lang on the <a> would mislabel the title too. In that case, put lang on a <span> around the name instead.
LangRelay's language switcher shows each language's own name by default and prints its links with no title attribute, so lang sits on the link itself. Stripped down to the attributes that matter here, a link looks like this:
<a href="https://www.lindenpottery.example/nl/workshops/" hreflang="nl" lang="nl"><span>Nederlands</span></a>How LangRelay sets both#
In LangRelay, each language a site translates into is one BCP 47 tag, such as de or nl. The hreflang value and the lang value are both that tag, and the URL prefix is its lowercase form, so /nl/ serves lang="nl" and is listed as hreflang="nl". A formality choice, such as German Sie or du, changes the wording of the translation and never the tag.
On a translated page, LangRelay's WordPress plugin sets lang on the <html> element to the language's tag, replacing what the theme printed or adding the attribute if the theme printed none. The original page keeps the value WordPress gives it from the site language, so check that the site language in WordPress matches the language your content is written in.
After a release, LangRelay fetches the translated URL and compares its lang with the language it published, alongside the canonical and the page's own hreflang entry. If they disagree, the page is flagged on the release.
How to check a live page#
curl -s https://www.lindenpottery.example/it/workshops/ | grep -o '<html[^>]*>'
curl -s https://www.lindenpottery.example/it/workshops/ | grep -i 'hreflang'The first line should show the page's language. The second should include an entry for that same language pointing at the URL you fetched. An accessibility checker such as axe reports a missing lang as html-has-lang.
The rest of the cluster covers the other entries in the head: hreflang and canonical tags, self-referencing hreflang and x-default, hreflang in XML sitemaps and hreflang in WordPress. The multilingual SEO page sums up what the plugin does for search, and you can book a demo to see it on your own site.
Sources
The pages this one cites, with the day each was read.
- 01lang (MDN Web Docs, last modified 2026-09-08)developer.mozilla.org, checked 6 October 2026
- 02<link>: The External Resource Link element (MDN Web Docs, last modified 2026-05-20)developer.mozilla.org, checked 6 October 2026
- 03HTML Living Standard, section 4.6 Links (WHATWG)html.spec.whatwg.org, checked 6 October 2026
- 04Understanding Success Criterion 3.1.1: Language of Page (W3C WAI, WCAG 2.2)w3.org, checked 6 October 2026
- 05Declaring language in HTML (W3C Internationalization)w3.org, checked 6 October 2026
- 06Choosing a language tag (W3C Internationalization)w3.org, checked 6 October 2026
- 07Localized versions of your pages (Google Search Central, last updated 2026-09-21)developers.google.com, checked 6 October 2026
- 08html-has-lang: <html> element must have a lang attribute (Deque University, axe 4.10)dequeuniversity.com, checked 6 October 2026
- 09get_language_attributes() (WordPress Developer Resources)developer.wordpress.org, checked 6 October 2026
- 10get_bloginfo() (WordPress Developer Resources)developer.wordpress.org, checked 6 October 2026



