Skip to content
Locale Lab

Chapter 11·Scripts and layout·~7 min

CJK font fallback & character coverage

A 'pure sans-serif' headline falls back to the OS default for one Hanzi character: a Japanese reader gets the Chinese glyph variant, a Chinese reader the Japanese one.

fontfallbackOS-specific

The problem

CJK Unified Ideographs share codepoints across Chinese, Japanese, and Korean, but the canonical glyph shapes differ. If the font stack doesn't include a script-specific CJK font, the browser falls back to the OS default, which often picks the wrong regional variant. Native speakers notice immediately, and the rendering differs between platforms, so QA on macOS won't catch what an Android user sees.

How it works

A font stack is a negotiation, one glyph at a time

font-family does not mean "use the first font". It means "for each character, use the first font in the list that has a glyph for it". A Latin headline face almost certainly contains no CJK glyphs. For those characters the browser walks past the whole stack and lands on an OS default, typically a different font on every platform. Nothing errors. One headline quietly mixes two or three fonts, with different metrics on each machine.

The larger issue is Han unification: Unicode assigns one code point to a character that Chinese and Japanese conventions draw differently. 直 (U+76F4) is the classic example. The code point cannot tell the font which shape a reader expects. Only the language can. That is the concrete job of the lang attribute: it steers font selection and glyph conventions for unified code points, and it also feeds line-breaking and assistive tech. Tag the page, and tag every span that switches language.

The demo

Try these, in order

Each step reproduces one specific failure in the demo below.

  1. 1
    On the default mixed sample, compare the “Naive (Helvetica/Arial)” row against the Noto row. The naive stack still renders CJK: the OS silently substituted a system font. The expandable per-font availability list shows which fonts exist on the current machine.
  2. 2
    Switch the sample to “Traditional Han characters”. 說 (traditional) and 说 (simplified) are two code points for one word, not one code point restyled. Watch which stacks have a glyph for 說 and which substitute a simplified- or Japanese-leaning face.
  3. 3
    Compare the measured pixel widths listed for each stack. The same string measures differently per stack. Fallback shifts layout and truncation behavior per platform, on top of the letterform change.
  4. 4
    Compare the “N of M fonts available on this device” line across the five stacks. The counts are a property of this machine rather than of the page. On another OS the same stacks produce different availability lists, substitutes, and rendering: the bug class that never reproduces on the reporter's machine.

The Han-trad / Han-simp samples look nearly identical in many stacks. Watch for this subtle bug when a product ships zh-Hans only.

Naive: Helvetica only0 of 2 fonts available on this device (approximate)
我们在TikTok上工作 (한국어, 日本語, English, 中文) all in one string.

font-family: 'Helvetica Neue', Arial, sans-serif

With no CJK font in the stack, the browser falls back to the system 'last-resort' font, which differs on macOS, Windows, and Android.

Per-font availability check
  • Helvetica Neue
  • Arial
Noto Sans (recommended)0 of 6 fonts available on this device (approximate)
我们在TikTok上工作 (한국어, 日本語, English, 中文) all in one string.

font-family: 'Inter', 'Noto Sans', 'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans TC', sans-serif

Google's Noto family aims to cover every Unicode script. Order matters: list the per-language variants (SC/JP/KR/TC) explicitly.

Per-font availability check
  • Inter
  • Noto Sans
  • Noto Sans SC
  • Noto Sans JP
  • Noto Sans KR
  • Noto Sans TC
System Chinese (zh-Hans focus)0 of 5 fonts available on this device (approximate)
我们在TikTok上工作 (한국어, 日本語, English, 中文) all in one string.

font-family: 'Inter', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Source Han Sans SC', sans-serif

Picks the native font per OS: PingFang on macOS, Microsoft YaHei on Windows.

Per-font availability check
  • Inter
  • PingFang SC
  • Hiragino Sans GB
  • Microsoft YaHei
  • Source Han Sans SC
System Japanese0 of 5 fonts available on this device (approximate)
我们在TikTok上工作 (한국어, 日本語, English, 中文) all in one string.

font-family: 'Inter', 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', 'Yu Gothic UI', 'Meiryo', sans-serif

Traditional or simplified Chinese characters that render here use Japanese glyph variants, subtly wrong.

Per-font availability check
  • Inter
  • Hiragino Kaku Gothic ProN
  • Hiragino Sans
  • Yu Gothic UI
  • Meiryo
Serif (Source Serif + Noto Serif CJK)0 of 5 fonts available on this device (approximate)
我们在TikTok上工作 (한국어, 日本語, English, 中文) all in one string.

font-family: 'Source Serif 4', 'Noto Serif', 'Noto Serif SC', 'Noto Serif JP', 'Noto Serif KR', serif

Editorial style for long-form blog content in mixed scripts.

Per-font availability check
  • Source Serif 4
  • Noto Serif
  • Noto Serif SC
  • Noto Serif JP
  • Noto Serif KR

Fallback happens per character

The font cascade runs per character, not per element. In a stack that lists Helvetica first, one Chinese character in a header renders in whatever font the OS picks, often a serif that breaks the visual hierarchy.

The fix: list a CJK family directly after the primary sans-serif. Use font-language-override or per-language CSS (:lang(ja), :lang(zh-Hans)) when a page mixes simplified, traditional, and Japanese Kanji. Otherwise the OS picks Han-unification glyph variants that look wrong in the user's target.

The short version

Declare lang and ship an explicit CJK font stack. Without them, the OS substitutes a default font: a different one per platform, with the wrong glyph variants.

What to do about it

  • Bundle a Noto-class CJK webfont (Noto Sans SC, Noto Sans JP, Noto Sans KR) with the correct script subset for each shipping locale.
  • Set the lang attribute on elements containing CJK text so the browser can pick the correct glyph variant from a shared font and trigger the right OS fallback.
  • Define separate CSS font-family stacks per locale (e.g. .font-cjk-zh, .font-cjk-ja) and apply via html[lang] selectors or component-level classes.
  • QA each CJK market on the platforms its users run. The fallback font is an OS decision, so one build renders different glyph shapes on macOS, Windows, Android, and iOS.

Where this comes up

Who it concerns

EngineeringDesignQA

Moments

  • ·Launch prep for a CJK market
  • ·Brand audit / design system review
  • ·Font-license procurement conversation with finance

Field note

Han unification gives Chinese, Japanese, and (historically) Korean the same code points for thousands of characters that the three traditions draw differently. The font decides which shape the reader sees, and the stack, the lang attribute, and the OS decide the font. Rendering without tofu does not mean the glyph shapes match the market's convention.

Wikipedia: Han unification ↗

Terms in this chapter

Where to read more

Related chapters