Home › Guides › HTML lang, Charset & Viewport: 60 Sites

HTML lang, Charset & Viewport: 60 Sites

60 homepages: one has no lang at all, three declare charset past the 1024-byte window, and four block zoom outright.

How this was measured

On 2026-10-17 we requested the homepage of each of the same 78 hand-picked domains our other surveys use — news, commerce, SaaS, developer tooling, social, streaming, reference, SEO, education, finance, cloud, local and government — and parsed the opening tag and the <head> of whatever came back. Each request identified itself as a survey crawler and was attempted directly first, then through a local proxy.

62 of the 78 returned HTML we could parse (79.5%) using a 2 KB floor, and two of those were not homepages at all: khanacademy.org sent a 3 KB page titled "Client Challenge" and paypal.com sent 8 KB of "Security Challenge … Please wait while we perform security check". Their heads are the wall's, not the site's, so any verdict drawn from them would be fiction. That leaves 60 homepages for everything below.

A further seven are JavaScript shells — reddit.com (8 KB), x.com, pinterest.com, tumblr.com, youtube.com, twitch.tv and airbnb.com return under 500 characters of extractable body text. We kept them. The distinction matters: <html lang>, charset and viewport live in the head, which servers do emit, so those three are measurable; whether the declared language matches the words on the page is not, and every content-level claim here is restricted to the 53 sites that yield at least 500 characters of text.

Three parsing rules shaped the numbers, and two of them changed an answer. Inline script, style, template and noscript blocks are masked to equal-length whitespace before parsing — a string inside a bundle is not an element, but masking must preserve length because we measure byte offsets. Attribute regexes carry a left boundary ((?:^|\s)lang\s*=), because data-lang="x" ends in lang= and will otherwise be matched. Bot walls are excluded by title and body text, not by size — a 3 KB challenge page and a 944 KB single-page app look nothing alike, and only one of them is a homepage.

Finding 1: one site in sixty has no language, and it is the one that needs it most

59 of the 60 homepages declare a language on <html> (98.3%). The exception is twitch.tv, whose opening tag is <html class="tw-root--hover"> — no lang, no dir.

That would be a footnote except for what sits 100 bytes later. twitch.tv declares 28 rel="alternate" hreflang entries plus an x-default: bg, cs, da, de, el, es, es-mx, fi, fr, hu, it, ja, ko, nl, no, pl, pt, pt-br, ro, ru, sk, sv, th, tr, vi, zh-cn, zh-tw. It has told Google exactly which URL serves which language in 28 languages and has not said which language the current document is in. Screen readers fall back to the user's default voice; a bilingual visitor gets whatever their OS guessed.

Among the 59 that do declare one, 37 write a bare en and 20 write a region tag (33.9%): en-us 15, en-gb 3, plus nodejs.org at en-gb — British English on a US foundation's site, which is a choice someone made and which nobody notices until a date format appears. Zero values fail a basic BCP 47 shape check, and zero are empty.

Four sites declared a language other than English, and all four are an artefact of where we asked from, not defects: stripe.com returned zh-cn with Chinese navigation, youtube.com ja-jp with a Japanese footer, linkedin.com zh, bloomberg.com ja. Our exit IP is in Asia. We report them because a language audit run from the wrong continent will "find" mismatches that do not exist, and because they are the reason Finding 5 compares primary subtags rather than whole strings.

Finding 2: two sites name eleven and thirteen languages but not their own

29 of the 60 homepages declare alternate hreflang entries, a median of 13 each and a maximum of 89 (stripe.com, then shopify.com 85, mozilla.org 81, wise.com 46, pinterest.com 37, figma.com 34). 20 of the 29 include x-default (69.0%).

Two do not include themselves. atlassian.com opens with <html lang="en"> and then lists ja, fr, de, es, pt-BR, zh-Hans, ko, ru, pl, it, nl — eleven languages, no en, no x-default. kubernetes.io opens with <html lang="en"> and lists bn, zh-cn, fr, de, hi, id, it, ja, ko, fa, pl, pt-br, ru, es, uk, vi — thirteen, no en. Both were checked against the raw tags, and both match what our earlier hreflang survey found from a different angle and a different snapshot: the English page is the one URL in the cluster that never gets an annotation.

Worth stating how easy it is to manufacture this finding by mistake. Our first comparison asked whether the literal string lang appears in the hreflang set, which flags atlassian.com's lang="en" against an en-us entry thirty times over. Comparing primary subtags, only these two survive.

Finding 3: every charset is UTF-8, and three declare it too late to matter

59 of 60 declare a character encoding and all 59 say UTF-8 — not one latin-1, not one legacy encoding. 58 do it with <meta charset> and one (squareup.com) with the older http-equiv="Content-Type". Six write it twice and none of the six disagree with themselves. youtube.com is the only site where the string charset does not appear anywhere in its 944 KB; it relies on the HTTP header, which is legal and which this method cannot see.

The position is where the interesting part is. Google's documented rule is that it looks at the first 1,024 bytes when sniffing an encoding. 54 of the 59 declare inside the first 512 bytes and the median offset is 88 — exactly where the spec wants it. Three do not:

cnn.com declares <meta charset="utf-8"> at byte 309,464. We read the raw file to confirm it: the declaration is real, it is the first one in the document, and what precedes it is a single inline <script> holding a JSON configuration blob roughly 300 KB long, itself preceded by font preloads. For any consumer that honours the 1,024-byte window, this page has no in-document encoding declaration at all.

slack.com at byte 11,218, behind a <script type="text/human"> containing an 11 KB ASCII-art Slack logo. A comment for human readers, shipped to every visitor, pushing the encoding declaration out of the window it needs to be in.

shopify.com at byte 1,986, written as <meta charSet="utf-8"/> — React's camelCase, which HTML treats identically since attribute names are case-insensitive, sitting behind a hydration-monitoring script.

None of the three is broken in a browser: all serve a UTF-8 Content-Type header, which wins. The finding is narrower and still useful — the in-document declaration, the one thing a saved file or a mirrored copy carries with it, has been moved somewhere it cannot do its job.

Finding 4: four homepages forbid zooming, and one of them writes "no" as 0

58 of 60 declare a viewport; youtube.com and twitch.tv do not. Of those 58, 56 write width=device-width. The content string is remarkably uniform: 23 distinct values across 58 sites, and 23 sites use the exact canonical width=device-width, initial-scale=1 — one in six homepages here is byte-identical on this line.

The exceptions are more interesting than the majority. wikipedia.org and wikimedia.org omit width entirely, writing only initial-scale=1,user-scalable=yes; an explicit initial scale is enough to make the layout viewport match the device, and the Wikimedia markup predates the convention. github.com, linkedin.com and netlify.com write width=device-width with no scale at all. bloomberg.com and tumblr.com ship two viewport tags each — Bloomberg's first is width=device-width and its second is width=device-width, initial-scale=1, viewport-fit=cover, so counting only the first would have reported a missing scale that is not missing.

Then there is zoom. Four homepages disable it outright (6.9%):

x.com writes width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0,viewport-fit=cover. netflix.com writes maximum-scale=1.0 and minimum-scale=1.0. screamingfrog.co.uk and vercel.com both write maximum-scale=1. target.com writes maximum-scale=2, which still permits doubling but caps it.

To count those four we had to fix our own parser. user-scalable has a documented keyword form (no) and an undocumented numeric form (0), and x.com uses the numeric one. Our first pass matched only no and reported zero sites disabling zoom — a completely clean result that was wrong. The maximum-scale check is what caught it. Three sites go the other way and affirmatively permit zoom (forbes.com, wikipedia.org, wikimedia.org with user-scalable=yes).

The rest of the vocabulary is thin: viewport-fit=cover on 6 sites, interactive-widget=resizes-content on 2 (walmart.com, target.com), and shrink-to-fit=no — a Safari-private keyword that has been obsolete for years — on 4: cnn.com, kubernetes.io, squareup.com, cdc.gov.

Finding 5: one in four still ships an Internet Explorer instruction

17 of the 60 homepages (28.3%) send <meta http-equiv="X-UA-Compatible">, a tag whose only purpose is to select a rendering engine in Internet Explorer 8 through 11. IE reached end of support in June 2022 and was removed from Windows by update in 2023. The values are a museum: IE=edge on cnn.com, target.com, netflix.com, moz.com, wise.com and others; IE=edge,chrome=1 on slack.com, asana.com, trello.com, linkedin.com, who.int — the chrome=1 half invoking a Chrome Frame plugin that was discontinued in 2014; IE=9 on spotify.com, which asks for a specific 2011 engine; and IE=Edge,chrome=IE7 on coursera.org, which contains two discontinued products in one attribute.

Nothing breaks. It is dead weight in the head of a quarter of the web, and it is the clearest available illustration of how long a tag survives after the reason for it is gone.

Finding 6: theme-color is on 26 sites, and one of them forgot the colour

26 of 60 declare theme-color, five with prefers-color-scheme media variants — x.com pairs #FFFFFF and #000000, vercel.com pairs #FAFAFA and #000. Five sites declare color-scheme (github.com and x.com at light dark, yoast.com at only light).

nodejs.org ships two theme-color tags with media queries and no content attribute on either: <meta name="theme-color" media="(prefers-color-scheme: light)"/> and the same for dark. We printed both tags to be sure. The element is valid HTML, no validator objects, and it instructs the browser to tint its UI with nothing. It is the same class of defect as an empty href: complete markup, no effect.

Eight sites set format-detection to stop iOS auto-linking phone numbers (all telephone=no, with stripe.com adding email=no) and five carry the apple-mobile-web-app-* trio for home-screen installation.

Finally, scale: 1,312 <meta> tags across the 60 heads, a median of 20.5 each, from 2 on reddit.com to 53 on github.com. A quarter of the way down the list the tags stop being about the document and start being about analytics, consent and app links.

What to ship

Put lang on <html> before you do anything else with language. One site in sixty here annotates 28 languages and cannot say what the current page is in. If you serve more than one language, add dir too — 14 sites in this sample do, all ltr.

Declare charset in the first 1,024 bytes, and keep it there. The median here is byte 88. The three failures are all self-inflicted: a 300 KB inline config script, an 11 KB ASCII-art comment, and a monitoring snippet. Anything large that runs before <meta charset> is a candidate for moving after it.

Write width=device-width, initial-scale=1 and nothing else. It is what 23 of 58 sites use verbatim. Do not add maximum-scale or user-scalable — four homepages here block zoom entirely and the World Wide Web Consortium's accessibility guidance treats that as a failure. If you must cap it, note that some sites write user-scalable=0 rather than no, so grep for the digit too.

Drop X-UA-Compatible and shrink-to-fit. 17 sites and 4 sites respectively, both targeting software that no longer exists.

If you write theme-color, give it a value, and prefer the media-variant pair. Two empty tags on nodejs.org are the cautionary example.

When you annotate hreflang, annotate your own page too. Two of 29 sites here list eleven and thirteen languages and omit the one they are serving.

To assemble these tags from one input so they cannot drift apart, the meta tag generator emits title, description, canonical, robots and icon tags together, and the hreflang generator builds a self-referencing language cluster with x-default included.

Limitations

Homepages only, 60 of them, one snapshot. Everything was fetched on 2026-10-17 with a crawler identity. WAFs answer differently per client, and an article page under a language switcher behaves differently from the root.

Server-rendered HTML only, no response headers. This is the largest gap in the method. youtube.com has no charset in its 944 KB and that is almost certainly a header declaration, which we did not capture — the same blind spot our resource-hint survey had to close by reading Link headers. Every "missing" here means "missing from the HTML", and for five sites the real answer may be in a header.

Seven of the 60 are JavaScript shells. Their heads are real, so lang/charset/viewport stand, but nothing about their content does. Content-level claims cover 53 sites.

Localisation. Our exit IP is in Asia and four sites answered with a regional variant. Language-mismatch detection is therefore unreliable at the margins, and we report no mismatch findings at all rather than four artefacts.

We counted markup, not behaviour. Whether a browser honours the second of two viewport tags, whether a blocked zoom fails an audit, and whether a late charset ever mis-decoded a page for anyone, are outside this method. A "defect" here means the declared intent differs from the common case, not that a user was harmed.

Reproduce it

The script and the shared domain list ship with this site. node scripts/survey-docmeta.mjs re-parses every number from the stored homepage HTML without touching the network, so a parsing fix can be applied and re-run months after the fetch; --evidence=lang, =charset, =viewport, =blocked, =hreflang and =theme print the original tag behind every verdict. It reads the most recent snapshot in .workbuddy/tmp by default, which today is the 2026-10-17 fetch; pass --raw=<file> to pin a specific one. That is what confirmed cnn.com's 309 KB offset against the raw file, caught x.com's numeric user-scalable=0, exposed nodejs.org's two valueless theme-color tags, and killed two findings that were our own parser's fault — the og:locale separator and the literal-string hreflang comparison. Our hreflang, favicon, indexing directive and image surveys run the same domain list from other angles.

About the author

Hongtao Ren (任宏涛) — Developer based in Xi'an, China. Builds browser-based tools and JetBrains IDE plugins. He built and maintains SerpPrism.

Corrections are the most useful thing you can send. If a tool or guide here gives you a wrong answer, that is a bug, not a judgement call — use the contact page.

Questions

Do I really need lang on the html element?

Yes. 59 of the 60 homepages we read declare it, and the one that does not (twitch.tv) declares 28 hreflang alternates — it annotates every language it serves except the one on the page you are looking at. lang picks the screen reader voice and the hyphenation dictionary, and browsers have no reliable fallback.

How late is too late for the charset declaration?

Google documents a 1,024-byte window. The median homepage here declares at byte 88 and 91.5% declare within 512 bytes — three do not: cnn.com at byte 309,464, slack.com at 11,218 and shopify.com at 1,986, each behind a large inline script or comment.

Does charset have to be written as meta charset?

No. 58 of 59 sites here use and one (squareup.com) uses http-equiv="Content-Type", which is equally valid. case also does not matter: shopify.com writes charSet because React does, and HTML attribute names are case-insensitive. What matters is that it is UTF-8 and that it is early.

Is maximum-scale=1 an accessibility problem?

Yes. It prevents pinch-zooming, which the W3C's accessibility guidance treats as a failure. Four of the 58 homepages here disable zoom: x.com (maximum-scale=1 plus user-scalable=0), netflix.com, screamingfrog.co.uk and vercel.com. target.com caps at maximum-scale=2, which is less severe but still a cap.

Why did my audit miss x.com blocking zoom?

Because user-scalable has two forms. The documented keyword is no, and x.com writes the numeric 0. Our own first pass matched only the keyword and reported zero sites blocking zoom. Grep for both: user-scalable\s*=\s*["']?(no|0).

What is the correct viewport meta tag?

width=device-width, initial-scale=1. 23 of the 58 sites that declare a viewport use exactly that string. Two Wikimedia sites omit width and rely on initial-scale alone, which also works; three sites (github.com, linkedin.com, netlify.com) write width with no scale at all.

Should I still include X-UA-Compatible?

No. 17 of 60 homepages here still do, including spotify.com at IE=9 and coursera.org at IE=Edge,chrome=IE7 — Chrome Frame was discontinued in 2014 and Internet Explorer reached end of support in 2022. The tag does nothing today except add bytes to the head.

How do I check my own page for these?

Read the first 1,024 bytes and the head, not the inspector: curl -s https://example.com/ | head -c 1024 | grep -i charset for position, then grep the head for ]*lang=, name="viewport" and theme-color. Confirm theme-color actually has a content value — nodejs.org ships two without one.