Skip to content

[mediaqueries-5] Add a prefers-lang media feature to expose user language preference #13615

@Evoke4350

Description

@Evoke4350

There is currently no CSS mechanism to match against the user's preferred language. :lang() matches the document's declared language (lang attribute, Content-Language header), not what the user's browser is configured to prefer.

On static hosting (GitHub Pages, S3, any CDN without edge compute), there is no server-side component to read Accept-Language and set <html lang> dynamically. The only option is JavaScript:

document.documentElement.lang = navigator.language.slice(0, 2);

This works but it forces a script dependency for something that should be declarative. Other user preferences already have media features — prefers-color-scheme, prefers-reduced-motion, prefers-contrast, prefers-reduced-transparency. Language is missing.

Use case

All 25 translations live in the DOM. CSS handles the rest — no JS, no server:

<span class="msg" lang="en">android will become a locked-down platform</span>
<span class="msg" lang="fr">android va devenir une plateforme fermée</span>
<span class="msg" lang="de">android wird eine geschlossene plattform</span>
.msg { display: none; }
:lang(en) > .banner .msg[lang="en"] { display: inline; }
:lang(fr) > .banner .msg[lang="fr"] { display: inline; }

This machinery works today. But :lang() only reads what the document declares, not what the user prefers. Bridging that gap requires JS or edge compute — neither should be necessary for what is a styling decision.

Proposal

@media (prefers-lang: fr) {
  .msg[lang="fr"] { display: inline; }
}

@media (prefers-lang: en) {
  .msg[lang="en"] { display: inline; }
}

Matching should follow BCP 47 prefix rules: (prefers-lang: en) matches en, en-US, en-GB, etc. This is consistent with how :lang() already handles subtag matching.

Privacy / fingerprinting

navigator.language is already exposed to JavaScript unconditionally — no permission, no opt-in. Any page running JS has this signal today. A CSS media feature does make it passive rather than active, which is a real tradeoff.

Counterpoints:

  1. prefers-color-scheme went through this debate ([css-mediaqueries] Expose User Preference Media Queries as HTTP Client Hint or HTTP Header #4162) and shipped. Language preference is not meaningfully more sensitive.
  2. The Accept-Language header is sent on every HTTP request. Servers already see this passively. CSS parity does not expand the attack surface for server operators.
  3. Sites that need i18n already use JS to detect language. A media feature removes the JS tax without creating a new fingerprinting vector — the information is already available.
  4. Browsers could expose a coarsened value (primary subtag only) rather than the full Accept-Language list, limiting the entropy.

Prior art

What this enables

  • CSS-only i18n on static sites — no JS, no edge compute, no server
  • Localized UI labels, banners, and content using only HTML + CSS
  • Progressive enhancement for sites that already have lang-tagged content in the DOM

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions