Lesson 11·Unit 4 · Scripts and layout·Font stacks & glyph coverage·Lv 201·~7 min
CJK font fallback & character coverage
Your 'pure sans-serif' headline silently switches to the OS default for one Hanzi character, and the OS default is different on macOS, Windows, Android, and iOS. A Japanese user gets the Chinese glyph variant. A Chinese user gets the Japanese one. Both are wrong.
By the end
- ·Explain Han unification: one code point, several regionally correct shapes, and the font picking between them.
- ·Predict what a browser does when a font lacks a glyph: fall down the stack character by character, landing on an OS default.
- ·Say what lang="ja" changes for a unified code point, and why a CJK launch needs a font stack per script.
The problem
CJK Unified Ideographs share codepoints across Chinese, Japanese, and Korean, but the canonical glyph shapes differ. If your 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 read it as a sign that you didn't take the locale seriously. Worse: 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". Your Latin headline face almost certainly contains no CJK glyphs. For those characters the browser walks past your 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.
See it yourself
Try these, in order
Each step triggers a specific failure you should recognize on sight.
- 1On 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. Expand the per-font availability list to see which fonts exist on your machine.
- 2Switch the sample to “Traditional Han characters”. 説 vs 说: watch which stacks render the traditional form with correct glyph conventions, and which fall back to a simplified-leaning font.
- 3Compare the measured pixel widths listed for each stack. The same string measures differently per stack. Silent fallback shifts layout and truncation behavior per platform, in addition to the letterform change.
- 4Compare the “N of M fonts available on this device” line across the five stacks. The counts are a property of this machine, not 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 you ship zh-Hans only.
font-family: 'Helvetica Neue', Arial, sans-serif
The stack lists no CJK fonts. The browser falls back to the system 'last-resort' font. The result differs on macOS, Windows, and Android.
Per-font availability check
- Helvetica Neue
- Arial
font-family: 'Inter', 'Noto Sans', 'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans TC', sans-serif
Google's Noto family covers nearly 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
font-family: 'Inter', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Source Han Sans SC', sans-serif
This stack picks the correct 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
font-family: 'Inter', 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', 'Yu Gothic UI', 'Meiryo', sans-serif
Japanese-first stack. 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
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
CSS fonts cascade per character, not per element. Suppose your stack lists Helvetica first and a header contains one Chinese character. That character silently renders in whatever font the OS picks, often a serif that breaks the visual hierarchy.
The fix: list a CJK family directly after your primary sans-serif. Use font-language-override or per-language CSS (:lang(ja), :lang(zh-Hans)) when you mix simplified, traditional, and Japanese Kanji on the same page. Otherwise the OS picks Han-unification glyph variants that look wrong in the user's target.
If you remember one thing
Declare lang and ship an explicit CJK font stack. When you do not, the OS silently 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 the locale you're shipping. - Set the
langattribute 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 viahtml[lang]selectors or component-level classes. - Always QA in the target locale's primary OS: Windows for SC, macOS & iOS for JP/TC, Android for KR. A native speaker testing on the wrong OS misses half the bugs.
Use this with
Stakeholders
Moments
- ·Launch prep for a CJK market
- ·Brand audit / design system review
- ·Font-license procurement conversation with finance
See it in a market: Japan: ships with a custom webfont requirement →
Field note
Han unification gives Chinese, Japanese, and (historically) Korean the same code points for thousands of characters: U+76F4 直 is one code point, but its expected shape differs between Chinese and Japanese conventions. The font decides which shape your user sees, and your stack, the lang attribute, and the OS decide the font. That a CJK string renders proves nothing. Check whether it renders with the shapes this market expects.
Quick check
3 questions · pass at 2+
Question 1/3
What is “Han unification” in Unicode?
Question 2/3
The first font in your font-family lacks a glyph the text needs. What does the browser do?
Question 3/3
Why does <html lang="ja"> (or lang on the element) matter for CJK rendering?