CSS font-language-override: pick which language's glyphs a font uses

Many fonts draw some letters differently for different languages. font-language-override tells the font which language's set to use, instead of the one it would pick from the lang attribute.

The CSS font-language-override property tells a font which language's letter forms to use. By default the browser works that out from the lang attribute. Setting a quoted OpenType language tag, such as "SRB" for Serbian, replaces that choice for the font only.

Try it. Both lines are Macedonian text in Cambria italic. The second one also has font-language-override, and the menu sets its value.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>font-language-override basics</title>
<style>
  /* Cambria ships with Windows. Declaring it through @font-face lets the script
     below redraw the text in Chrome and Edge. */
  @font-face {
    font-family: 'My Cambria';
    src: local('Cambria Italic');
  }

  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .card { background: #fff; border-radius: 12px; padding: 12px 14px; margin-bottom: 10px; }
  .label { font: 13px ui-monospace, Consolas, monospace; color: #5b6472; }
  .text { font-family: 'My Cambria', Cambria, serif; font-style: italic; font-size: 28px; margin: 4px 0 0; }
  #over { font-language-override: "SRB"; }   /* the one line that matters */
  select { font: inherit; font-size: 14px; }
  .note { font-size: 13px; color: #9a3412; margin: 8px 0 0; }
</style>
</head>
<body>
<div class="card">
  <div class="label">lang="mk"</div>
  <p class="text" lang="mk">б г д п т · Никој човек</p>
</div>
<div class="card">
  <div class="label">lang="mk" + font-language-override:
    <select id="pick">
      <option value='"SRB"'>"SRB"</option>
      <option value="normal">normal</option>
      <option value='"srb"'>"srb"</option>
      <option value='"XYZ"'>"XYZ"</option>
    </select>
  </div>
  <p class="text" id="over" lang="mk">б г д п т · Никој човек</p>
</div>
<p class="note" id="note" hidden></p>

<script>
  const over = document.getElementById('over');
  const note = document.getElementById('note');

  // Set the property from the menu
  document.getElementById('pick').addEventListener('change', (e) => {
    over.style.fontLanguageOverride = e.target.value;
    // Chrome and Edge kept the old glyphs in our test until the text was rebuilt
    over.innerHTML = over.innerHTML;
  });

  // Tell the reader when the example cannot show a difference
  function warn(text) { note.textContent = text; note.hidden = false; }
  if (!CSS.supports('font-language-override', 'normal')) {
    warn('This browser does not support font-language-override, so both lines look the same.');
  } else {
    const face = [...document.fonts].find((f) => f.family.includes('My Cambria'));
    face.load().catch(() => warn('Cambria is not installed here, so both lines look the same.'));
  }
</script>
</body>
</html>
Top: lang="mk" alone. Bottom: the same text with font-language-override. Pick a value to compare.

The example needs Cambria, which ships with Windows. Elsewhere it shows a note, because the difference depends on forms stored inside the font.

The steps for any text:

  1. Mark the language with lang, for example lang="mk".
  2. Find a tag the font supports, from a related language whose forms fit.
  3. Set the property, in capitals and in quotes, after any font shorthand.
:lang(mk) { font-language-override: "SRB"; }

What font-language-override changes

An OpenType font can hold different forms for the same letter, grouped by language system. The browser normally asks for the language system that matches the content language. With lang="mk", that is Macedonian, tag MKD.

lang alone asks the font for MKD forms. The override asks for SRB instead, while the text stays Macedonian.
lang alone asks the font for MKD forms. The override asks for SRB instead, while the text stays Macedonian.

Cambria has Serbian forms but no Macedonian ones, so Macedonian text gets the default shapes. The CSS Fonts spec uses this exact case as its example: a Macedonian page set to "SRB" to borrow Serbian typographic conventions from a font that has them.

MDN gives a similar case with Azeri text in a font without Azeri rules, where Turkish forms can be forced instead.

The property is inherited and its initial value is normal. normal means the browser infers the language system from the content language, as it does without the property.

The value: a quoted OpenType tag

The value is either normal or a string holding one OpenType language system tag. The tags come from the registry Microsoft publishes with the OpenType spec.

Language Tag lang value
Serbian "SRB" sr
Macedonian "MKD" mk
Bulgarian "BGR" bg
Romanian "ROM" ro
Russian "RUS" ru
Capitals, quotes, and after the font shorthand. An unknown or lower-case tag changes nothing.
Capitals, quotes, and after the font shorthand. An unknown or lower-case tag changes nothing.

A few rules decide whether the value does anything:

  • Case matters. The spec defines the tag as case-sensitive. "srb" did nothing in our tests.
  • Padding is automatic. Registry tags are four characters, like 'SRB ' with a trailing space. The spec pads shorter strings with spaces before matching.
  • Unknown tags are ignored. The spec says so, and "XYZ" left the text unchanged.
  • Quotes are required. Without them the value is not a string, and the declaration is invalid.

lang or font-language-override?

lang says what language the text is. The override only changes which glyph set of the font is used. The text stays Macedonian for :lang(mk) selectors and for hyphens: auto, which picks hyphenation rules from the language.

lang describes the text. font-language-override only changes the glyph set the font is asked for.
lang describes the text. font-language-override only changes the glyph set the font is asked for.

So set lang correctly first. When the font already has forms for that language, you do not need the override at all.

The property also cannot add forms a font does not have. If the font has no language-specific forms for the tag, OpenType falls back to its default set, and nothing visible changes.

Compare lang, the override and the traps

The Cambria rows use the same five letters. Look at which rows match the Macedonian row at the top and which match the Serbian one.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>lang vs font-language-override</title>
<style>
  body { margin: 0; padding: 14px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px 10px;
         background: #fff; border-radius: 10px; padding: 8px 12px; margin-bottom: 6px; }
  code { font: 12.5px ui-monospace, Consolas, monospace; color: #5b6472; }
  .s { font-family: Cambria, serif; font-style: italic; font-size: 26px; white-space: nowrap; }
  .note { font-size: 13px; color: #5b6472; margin: 0 0 8px; }
  .ro { font-family: Verdana, sans-serif; font-style: normal; font-size: 24px; }

  .srb { font-language-override: "SRB"; }
  .rus { font-language-override: "RUS"; }
  .low { font-language-override: "srb"; }                 /* tags are case-sensitive */
  .reset { font-language-override: "SRB"; font: italic 26px Cambria; }  /* font resets it */
  .rom { font-language-override: "ROM"; }
  .xyz { font-language-override: "XYZ"; }                 /* unknown tags are ignored */
</style>
</head>
<body>
<p class="note">Needs Cambria and Verdana, which ship with Windows. Without them, every row looks the same.</p>
<div class="row"><code>lang="mk"</code><span class="s" lang="mk">б г д п т</span></div>
<div class="row"><code>lang="sr"</code><span class="s" lang="sr">б г д п т</span></div>
<div class="row"><code>lang="mk" + "SRB"</code><span class="s srb" lang="mk">б г д п т</span></div>
<div class="row"><code>lang="sr" + "RUS"</code><span class="s rus" lang="sr">б г д п т</span></div>
<div class="row"><code>lang="mk" + "srb"</code><span class="s low" lang="mk">б г д п т</span></div>
<div class="row"><code>"SRB", then font: ...</code><span class="s reset" lang="mk">б г д п т</span></div>
<div class="row"><code>lang="mk" + "XYZ"</code><span class="s xyz" lang="mk">б г д п т</span></div>
<div class="row"><code>lang="en"</code><span class="ro" lang="en">Ş ş Ţ ţ</span></div>
<div class="row"><code>lang="en" + "ROM"</code><span class="ro rom" lang="en">Ş ş Ţ ţ</span></div>
</body>
</html>
Rows with the same shapes as row 1 got no Serbian forms. The last two rows use Verdana, where "ROM" swaps the cedilla for a comma below.

What the rows show:

  • lang="sr" alone gets the Serbian forms, because the font has them.
  • lang="mk" + "SRB" gets them too. That is the property doing its job.
  • lang="sr" + "RUS" goes the other way: the override wins over the language.
  • "srb" and "XYZ" change nothing.
  • A font shorthand written after the override resets it to normal, as the spec lists it among the properties the shorthand resets.

The font-variant shorthand, unlike font, does not reset it. Low-level OpenType switches live in a separate property; font-feature-settings covers those.

Browser support

MDN's compatibility data lists font-language-override in Chrome, Edge and Firefox, with support in Chrome added recently. It lists no support in Safari.

The CSS Fonts 4 spec lists the property as at risk, a W3C term for features that may be dropped before the spec is finished.

In our tests, WebKit ignored the property while still using lang, so the text kept the forms that lang alone gives. To show a note where it is missing, test for it in CSS:

@supports not (font-language-override: normal) {
  .warn { display: block; }
}

Chrome needed extra care in our tests. It kept the old glyphs after a script changed the value, until the text was rebuilt. The examples here run el.innerHTML = el.innerHTML after each change.

That rebuild only helped with a font declared in @font-face with src: local(). With the installed font named directly in font-family, even a rebuild did not redraw it. The font-palette guide uses the same local() declaration.

Two more Chrome cases:

  • Identical text. When the same text in the same font and size appeared earlier on the page, the later copy sometimes reused its shapes. Compare with different words.
  • Child elements. A child element inheriting the value sometimes kept the default forms. A :lang(mk) rule, which matches every element in the passage, avoided it.

A finished example: a Macedonian quote

A page in English quotes a passage in Macedonian. One rule gives every Macedonian passage the Serbian forms, and the English text is untouched.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A Macedonian quote with Serbian forms</title>
<style>
  @font-face { font-family: 'My Cambria'; src: local('Cambria Italic'); }

  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  article { background: #fff; border-radius: 12px; padding: 14px 16px; max-width: 560px; }
  h2 { margin: 0 0 6px; font-size: 18px; }
  p { margin: 0 0 10px; font-size: 15px; line-height: 1.5; }
  blockquote { margin: 0 0 10px; padding: 8px 14px; border-left: 4px solid #0f766e; background: #f0fdfa;
               font-family: 'My Cambria', Cambria, serif; font-style: italic; font-size: 21px; line-height: 1.4; }

  /* The whole fix: every Macedonian passage uses the font's Serbian forms */
  .fix :lang(mk) { font-language-override: "SRB"; }

  label { display: flex; gap: 8px; align-items: center; font-size: 14px; }
  output { display: block; font: 13px ui-monospace, Consolas, monospace; color: #5b6472; margin-top: 6px; }
  .warn { display: none; font-size: 13px; color: #9a3412; margin-top: 8px; }
  @supports not (font-language-override: normal) { .warn.support { display: block; } }
</style>
</head>
<body class="fix">
<article>
  <h2>Article 9</h2>
  <p>No one shall be subjected to arbitrary arrest, detention or exile. In Macedonian:</p>
  <blockquote lang="mk" id="q">Никој човек нема да биде подложен на произволно апсење, притвор или прогонување.</blockquote>
  <label><input type="checkbox" id="on" checked> Apply <code>:lang(mk)</code> rule</label>
  <output id="out"></output>
  <p class="warn support">This browser does not support font-language-override, so the switch changes nothing.</p>
  <p class="warn" id="nofont">Cambria is not installed here, so the switch changes nothing.</p>
</article>

<script>
  const q = document.getElementById('q');
  const out = document.getElementById('out');

  function show() {
    // Read back what the browser actually uses for the quote
    out.textContent = 'computed: ' + (getComputedStyle(q).fontLanguageOverride || 'not supported');
  }
  document.getElementById('on').addEventListener('change', (e) => {
    document.body.classList.toggle('fix', e.target.checked);
    q.innerHTML = q.innerHTML;  // Chrome and Edge redrew the glyphs only after this in our test
    show();
  });
  show();

  // Only worth checking the font where the property is supported
  if (CSS.supports('font-language-override', 'normal')) {
    const face = [...document.fonts].find((f) => f.family.includes('My Cambria'));
    face.load().catch(() => { document.getElementById('nofont').style.display = 'block'; });
  }
</script>
</body>
</html>
The :lang(mk) rule applies SRB to the quote. Untick the box to see the font's default forms.
  • One rule for the language: .fix :lang(mk) matches the quote and anything inside it, so new quotes need only lang="mk".
  • A switch to compare: the checkbox toggles the class on <body> and rebuilds the quote's text for Chrome.
  • Read it back: the script prints the computed value, so you can see what the browser holds. Where the property is unsupported, it has no value to read.
  • Two notes: one for browsers without support, one when Cambria is not installed.
out.textContent = getComputedStyle(q).fontLanguageOverride;

When it does not work

What you see Cause Fix
Nothing changes The font has no forms for that tag Use a font that has them, or another tag
Nothing changes Lower-case or unknown tag Use the registered tag in capitals
The declaration is dropped The tag is not in quotes Write "SRB", not SRB
Works, then stops A later font shorthand reset it Put the override after font
No change in Safari Not supported there Rely on lang; show a note with @supports
No change after a script sets it (Chrome) Text already drawn keeps its glyphs Rebuild the text; declare the font with local()
A child element keeps the old forms (Chrome) Inherited value not applied Target :lang(mk) instead of a parent class

A screenshot of glyph shapes only shows how they look in one browser. The difference depends on the fonts installed and on support, so a reviewer should open the page in their own browser.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written and its scripts run, so the people you send it to can try the menu and the switch themselves. If you change the code later, the same link shows the new version.

Questions people ask

What does font-language-override do?

It sets the OpenType language system the font uses for glyph choices, in place of the one the browser infers from the content language. The value is normal or a quoted tag such as "SRB". The language of the text itself does not change.

Should I use font-language-override or the lang attribute?

Start with lang. It tells the browser the real language of the text, and with the default value normal the font already uses that language's forms. Add font-language-override only when the font has no forms for that language and a related language's forms fit.

Why does font-language-override do nothing?

The usual causes: the font has no forms for that tag, the tag is in lower case (tags are case-sensitive), the tag is unknown, a later font shorthand reset it, or the browser does not support the property. MDN's compatibility data does not list Safari support.

Where do I find the tag for a language?

In the OpenType language system tag registry published by Microsoft. Serbian is SRB, Macedonian is MKD, Romanian is ROM and Bulgarian is BGR. In the registry each tag is four characters, padded with a trailing space; CSS adds that padding for you.

Keep reading