# Language attributes: lang on the page and on mixed-language parts

> The lang attribute picks the screen reader voice, the hyphenation and the quote marks. Learn the right BCP 47 value, when to mark a phrase in another language, and how lang differs from hreflang.

Updated 2026-09-25 · Accessibility · HTML version: https://getreport.app/guides/language-attributes-and-screen-readers

`<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 `lang` on the `<html>` element of every page, with a [BCP 47](https://www.w3.org/International/questions/qa-html-language-declarations) tag: `hr`, `en`, `en-GB`, `de-AT`, `pt-BR`, `zh-Hant`.
- Language first, region optional, hyphen between: `en-GB` is right, `en_GB`, `english` and `GB` are not.
- Mark passages in another language with `lang` on the element: `<blockquote lang="fr">`. Names, brand names and loan words do not need it.
- `hreflang` and `Content-Language` do different jobs (which page version to show, and the intended audience). Neither replaces `lang`.
- 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](https://getreport.app/tools/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](https://www.w3.org/WAI/WCAG22/Understanding/language-of-page.html), the lowest bar there is.

Screen readers are the loudest beneficiary but not the only one:

- **Hyphenation**: `hyphens: auto` in 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 by `lang`.
- **Fonts**: Chinese, Japanese and Korean share code points; `lang="ja"` versus `lang="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

> **Free tool:** [Accessibility checker (WCAG 2.2)](https://getreport.app/tools/accessibility-checker): axe-core violations by impact, colour contrast, landmarks, form labels, link names, tap targets and zoom — measured on the rendered page, with the selectors to fix.

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 accessibility panel on a page with no lang attribute: the language finding sits among the results with its why text and the WordPress hint, next to the landmark and skip link findings](https://getreport.app/guides/img/language-attributes-and-screen-readers/lang.webp "A missing language rarely travels alone; the same template usually also lacks landmarks and a skip link.")

> **Check: The page declares its language.** Screen readers pick their pronunciation rules from the lang attribute on <html>. Without it, Croatian text may be read with English rules and become impossible to follow.
>
> 1. Add lang to the html element, for example <html lang="hr"> or <html lang="en-GB">.
> 2. In WordPress the theme prints it from Settings → General → Site Language.

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.

> **Check: The <html> element declares its language.** The lang attribute tells search engines and translation tools which language the page is in, and lets screen readers pick the right pronunciation.
>
> 1. Add lang to the html element, for example <html lang="hr"> or <html lang="en-GB">.
> 2. In WordPress the theme prints it from Settings → General → Site Language.

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).

> **Check: hreflang tags are complete and valid.** hreflang tells Google which language or country version to show each searcher. A missing self-reference or an invalid code makes the whole set ignored, so visitors land on the wrong version.
>
> 1. Use valid codes (language, optionally a region; for example "en", "en-GB", "de-AT") and at most one "x-default".
> 2. Include this page's own URL in its hreflang set, and list the same set on every version.

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](https://getreport.app/guides/hreflang-x-default-explained) 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

```html
<!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
<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](https://www.w3.org/WAI/WCAG22/Understanding/language-of-parts.html), 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:

```html
<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="https://getreport.app/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:

```html
<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:

1. **Hard-coded**: `header.php` reads `<html lang="en">` because the theme author typed it. Replace it with `<html <?php language_attributes(); ?>>` in the child theme's `header.php`.
2. **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.
3. **Multilingual plugins**: WPML and Polylang set the attribute per language automatically and add the `hreflang` links; 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.lang` returns 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_US` comes from operating system locales and fails validation in browsers and in the report. Use `en-US`.
- **Country instead of language**: `lang="uk"` on a British site announces Ukrainian; `lang="jp"` is not Japanese (that is `ja`).
- **Language name instead of code**: `lang="english"` or `lang="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.
- **`lang` on 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.
