
What is typography for CJK languages?
- Sajjad
- Typography
- 11 Oct, 2026
CJK typography is the practice of setting Chinese, Japanese and Korean text, three writing systems that share Han characters and a square, grid-based design but differ in scripts, punctuation and line-breaking rules. Each character sits in a full-width square called the em box, text has no spaces between words in Chinese and Japanese, and lines can break between almost any two characters, subject to rules about punctuation. CJK text can be set horizontally or vertically, uses its own emphasis conventions instead of italics, and needs fonts with thousands or tens of thousands of glyphs. On the web, getting it right depends on the lang attribute, a careful font stack, a few CSS properties for line breaking and spacing, and sensible loading for very large font files.
Designers who work mostly with Latin text often apply Latin habits to CJK pages, such as italics, tight line height, wide letter spacing and a single "Asian" font for all three languages, and the results look wrong to native readers. In this article you'll learn how CJK text is structured, why the language tag changes the glyphs you see, which fonts to use, how to control line breaking and spacing, how to set vertical text and ruby annotations, and how to load CJK fonts without crippling page speed.
How CJK Text Is Structured
The three languages share a family resemblance but are not interchangeable.
- Chinese: Written almost entirely in Han characters (hanzi). Simplified Chinese is used in mainland China and Singapore, Traditional Chinese in Taiwan, Hong Kong and Macau. Text has no spaces between words.
- Japanese: Mixes kanji (Han characters) with two syllabaries, hiragana and katakana, plus Latin letters and Western digits. Text has no spaces between words.
- Korean: Written mainly in Hangul, an alphabet whose letters are grouped into square syllable blocks. Modern Korean uses spaces between words, and Han characters (hanja) appear only occasionally.
All three are designed on a square grid. A typical CJK character occupies the full em square, so a 16px font produces characters that are roughly 16px wide and 16px tall. Punctuation in Chinese and Japanese is also full-width by default, meaning a comma or full stop takes up a whole square, with the visible mark placed in one corner or the centre.
That grid is part of the aesthetic. Good CJK typesetting keeps characters aligned in neat columns and rows, which is why careless mixing of proportional Latin text and full-width punctuation can look messy.
Why the lang Attribute Matters So Much
Unicode encodes many Han characters only once, even when Chinese, Japanese and Korean traditions draw them slightly differently. This is called Han unification. The same code point can need different glyph shapes depending on the language, so the font has to know which language it's setting.
Browsers use the lang attribute to pick both the font fallback and, in multi-language fonts such as Source Han Sans and Noto Sans CJK, the correct regional glyphs. Without it, a Japanese page may render with Chinese glyph forms, which looks subtly but definitely wrong to Japanese readers.
<!-- Japanese -->
<html lang="ja">
<!-- Simplified Chinese -->
<html lang="zh-Hans">
<!-- Traditional Chinese, Taiwan -->
<html lang="zh-Hant-TW">
<!-- Traditional Chinese, Hong Kong -->
<html lang="zh-Hant-HK">
<!-- Korean -->
<html lang="ko"></html>
</html>
</html>
</html>
</html>
Use the most specific tag you can. zh alone doesn't tell the browser whether you want Simplified or Traditional forms. If a page mixes languages, mark each passage:
<p lang="en">
The word for "bridge" is <span lang="ja">橋</span> in Japanese and
<span lang="zh-Hans">桥</span> in Simplified Chinese.
</p>
Choosing CJK Fonts
CJK fonts are large projects. A Japanese font needs several thousand kanji plus kana; a Chinese font for general use typically covers many thousands of characters; and a Korean font that supports all modern Hangul syllables needs 11,172 of them. That's why the choice of free, high-quality web fonts is narrower than for Latin.
Common System Fonts
Each operating system ships good CJK fonts, and leaning on them is often the fastest option.
| Language | macOS and iOS | Windows | Android and Linux |
|---|---|---|---|
| Japanese | Hiragino Sans, Hiragino Mincho ProN | Yu Gothic, Meiryo | Noto Sans CJK JP |
| Simplified Chinese | PingFang SC | Microsoft YaHei | Noto Sans CJK SC |
| Traditional Chinese | PingFang TC | Microsoft JhengHei | Noto Sans CJK TC |
| Korean | Apple SD Gothic Neo | Malgun Gothic | Noto Sans CJK KR |
Open-Source Web Fonts
- Noto Sans CJK and Noto Serif CJK: Developed by Google and Adobe, and released by Adobe as Source Han Sans and Source Han Serif. They cover all three languages with regional glyph variants and multiple weights.
- Noto Sans JP, Noto Sans SC, Noto Sans TC, Noto Sans KR: Per-language versions available on Google Fonts.
- Pretendard: A widely used open-source Korean sans-serif that pairs Hangul with Latin glyphs.
- Zen Kaku Gothic, M PLUS 1p, BIZ UDPGothic: Japanese families on Google Fonts with different personalities.
Writing a Font Stack
List a Latin font first if you want its Latin glyphs for embedded English and digits, then the CJK fonts for each platform, then a generic family. Use :lang() so each language gets its own stack.
:root {
--font-ja:
"Inter", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP",
"Yu Gothic", Meiryo, sans-serif;
--font-zh-hans:
"Inter", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
--font-zh-hant:
"Inter", "PingFang TC", "Noto Sans TC", "Microsoft JhengHei", sans-serif;
--font-ko:
"Inter", "Apple SD Gothic Neo", "Pretendard", "Noto Sans KR",
"Malgun Gothic", sans-serif;
}
:lang(ja) {
font-family: var(--font-ja);
}
:lang(zh-Hans) {
font-family: var(--font-zh-hans);
}
:lang(zh-Hant) {
font-family: var(--font-zh-hant);
}
:lang(ko) {
font-family: var(--font-ko);
}
Putting a Latin font first is a design choice. CJK fonts include their own Latin glyphs, which are designed to harmonise with the CJK characters, so some designers prefer to let the CJK font handle everything.
Size, Line Height and Line Length
Because CJK characters fill the em square and carry more visual detail than Latin letters, they need different settings.
- Font size: CJK characters with many strokes become hard to read at small sizes. A body size of 16px is a reasonable minimum, and many Chinese and Japanese sites use 16px to 18px for long-form text.
- Line height: Lines of dense characters need more space between them than Latin text. Values from about 1.7 to 2 are common for body copy.
- Line length: Measure lines in characters, not words. Around 30 to 40 characters per line is a widely used guide for comfortable Japanese and Chinese body text, which is a narrower column than you'd use for English. Korean, with its spaces, behaves more like a word-based language.
- Letter spacing: A very small amount of tracking, such as 0.02em to 0.05em, is sometimes added to body text for an airier look. Large values break the grid and should be avoided.
- No italics: CJK scripts have no italic tradition, and synthesised slanted characters look broken. Use bold, colour or emphasis marks instead.
:lang(ja),
:lang(zh) {
font-size: 1.0625rem;
line-height: 1.85;
letter-spacing: 0.03em;
}
:lang(ja) article,
:lang(zh) article {
max-inline-size: 38em;
}
:lang(ja) em,
:lang(zh) em,
:lang(ko) em {
font-style: normal;
font-weight: 700;
}
Because each character is roughly 1em wide, max-inline-size: 38em gives you about 38 characters per line, slightly fewer once punctuation and spacing are counted.
Line Breaking Rules
Chinese and Japanese text can break between almost any two characters, but some characters must never start or end a line. Closing brackets, full stops and small kana shouldn't start a line, and opening brackets shouldn't end one. In Japanese these rules are called kinsoku shori. Browsers apply them automatically, and CSS lets you choose how strictly.
line-break
:lang(ja) {
line-break: strict;
}
line-break: strict applies the full set of rules, preventing small kana and the prolonged sound mark from starting a line. normal relaxes some of them, and loose relaxes more, which can help in narrow columns.
word-break for Korean
Korean has spaces between words, but browsers may still break inside a word because Hangul syllables are treated like CJK characters. Most Korean readers prefer lines to break only at spaces:
:lang(ko) {
word-break: keep-all;
overflow-wrap: anywhere;
}
keep-all stops breaks inside words, and overflow-wrap: anywhere is a safety net so very long strings can still wrap rather than overflow.
Phrase-Based Breaking for Japanese
Breaking Japanese between any two characters is correct, but it can split a short headline at an awkward point. Chromium-based browsers support word-break: auto-phrase, which uses language analysis to break between natural phrases. It only works when the lang is set to Japanese, and other browsers ignore it and fall back to normal breaking, so it's safe to use as an enhancement on headings.
h1:lang(ja),
h2:lang(ja) {
word-break: auto-phrase;
text-wrap: balance;
}
For an introduction to balancing headings, see text-wrap: balance and text-wrap: pretty.
Spacing Between Scripts and Punctuation
Two spacing problems come up constantly in CJK text: the gap between CJK characters and embedded Latin text, and the doubled-up white space when two full-width punctuation marks sit side by side.
Space Between CJK and Latin
Typesetters traditionally add a small gap, about a quarter of an em, where Han characters or kana meet Latin letters or digits. Typing a real space looks too wide and isn't how native writers type. The CSS text-autospace property adds that gap automatically. It's supported in recent versions of Chromium-based browsers and Safari, and other browsers simply show no extra space, so it's a safe progressive enhancement.
:lang(ja),
:lang(zh) {
text-autospace: normal;
}
Trimming Full-Width Punctuation
Because full-width punctuation occupies a whole square, a closing bracket followed by a full stop can leave a visibly large gap, and a paragraph that starts with an opening bracket appears to be indented by half a character. The text-spacing-trim property tells the browser to tighten these cases. It's supported in recent Chromium-based browsers and Safari.
:lang(ja),
:lang(zh) {
text-spacing-trim: normal;
}
For headings in Japanese, the OpenType feature palt (proportional alternate widths) tightens the spacing of kana and punctuation, giving a more compact display look. Avoid it in body text, where the even grid is easier to read.
h1:lang(ja),
h2:lang(ja) {
font-feature-settings: "palt";
}
Vertical Text
Chinese and Japanese can be written top to bottom, with columns running from right to left. Vertical text is still common in Japanese novels, newspapers and signage, and CSS supports it well.
.vertical {
writing-mode: vertical-rl;
text-orientation: mixed;
block-size: 24em;
line-height: 1.9;
}
.vertical .tcy {
text-combine-upright: all;
}
<div class="vertical" lang="ja">
<p>
令和<span class="tcy">8</span>年<span class="tcy">10</span
>月、新しい店が開きました。
</p>
</div>
writing-mode: vertical-rllays text in vertical lines that progress from right to left.text-orientation: mixedkeeps CJK characters upright and rotates Latin text sideways, which is the convention for longer runs of Latin.text-combine-upright: allsqueezes a short run, such as a two-digit number, into a single upright square. Japanese typesetters call this tate-chu-yoko.
Logical properties really pay off here. A component written with margin-block and padding-inline adapts to vertical text without changes.
Ruby and Emphasis Marks
Ruby Annotations
Ruby text is small annotation placed alongside characters to show pronunciation. Japanese uses furigana over kanji, and Chinese uses pinyin or bopomofo. HTML has dedicated elements for it, and browsers render them natively.
<p lang="ja">
<ruby>漢字<rp>(</rp><rt>かんじ</rt><rp>)</rp></ruby>を読む。
</p>
The rp elements provide brackets for browsers that don't support ruby, so the reading appears in parentheses instead of disappearing. You can position ruby with ruby-position, for example under for some Chinese styles.
Emphasis Marks
Instead of italics, Japanese and Chinese emphasise text with small dots placed beside each character. CSS supports this with text-emphasis:
.emphasis:lang(ja) {
text-emphasis: filled sesame;
}
.emphasis:lang(zh) {
text-emphasis: filled dot;
text-emphasis-position: under right;
}
Japanese uses sesame-shaped marks above horizontal text, while Simplified Chinese usually places dots below.
Loading CJK Web Fonts
A full CJK font can be several megabytes per weight, so loading one the way you'd load a Latin font can seriously slow down a page. You have a few practical options:
- Use system fonts: The quickest option is a good system stack, especially for body text.
- Use a service that slices the font: Google Fonts splits CJK fonts into many small files, each covering a group of characters with
unicode-range. The browser downloads only the slices that the page actually uses. - Subset for fixed text: For a logo, a headline or a short campaign page with known copy, subset the font to only the characters you need.
- Use
font-display: swap: Let text appear in a fallback font straight away rather than waiting for a large download.
Subsetting to exactly the characters on a page is easy with the fontTools pyftsubset tool:
pip install fonttools brotli
pyftsubset NotoSansJP-Bold.ttf \
--text-file=headline.txt \
--flavor=woff2 \
--layout-features='*' \
--output-file=headline-ja.woff2
ls -lh NotoSansJP-Bold.ttf headline-ja.woff2
-rw-r--r-- 1 user staff 5.6M NotoSansJP-Bold.ttf
-rw-r--r-- 1 user staff 14K headline-ja.woff2
The exact sizes depend on the font and your text, but the reduction for a short headline is always dramatic. Don't use a fixed subset for content that changes, such as user comments or CMS pages, because any character outside the subset will fall back to another font.
FAQ: CJK Typography
CJK stands for Chinese, Japanese and Korean. The term groups the three because they share Han characters and similar typographic conventions, although each language has its own scripts and rules. You will sometimes see CJKV, which adds Vietnamese for its historical use of Han characters.
You can use a multi-language family such as Noto Sans CJK or Source Han Sans, but you still need the correct lang attribute so the font shows the right regional glyph forms. A single-language font, such as one designed only for Japanese, will usually look wrong for Chinese or Korean text.
Chinese, Japanese and Korean have no italic tradition, so CJK fonts rarely include italic styles. Browsers fake italics by slanting the glyphs, which looks distorted. Use bold, colour, or emphasis marks with the text-emphasis property instead.
Body text usually needs a line height of roughly 1.7 to 2, which is noticeably more than for English. Dense characters need extra space between lines to stay readable, especially at smaller sizes.
Set word-break: keep-all on Korean text so lines break only at spaces, and add overflow-wrap: anywhere as a fallback so very long strings can still wrap instead of overflowing the container.
Full CJK fonts are large, often several megabytes per weight, but you can still use them. Services such as Google Fonts split them into small unicode-range slices, and you can subset fonts yourself for fixed text like headlines. For body text, system fonts are often the best choice.
Conclusion
CJK typography is built on a square grid of full-width characters, with no word spaces in Chinese and Japanese, strict rules about which characters can begin or end a line, and conventions such as vertical text, ruby annotations and emphasis marks that have no direct Latin equivalent. On the web, the single most important step is setting a precise lang attribute, because it controls font fallback, regional glyph shapes and line-breaking behaviour.
From there, use a language-specific font stack, give text more size and line height than you would for English, keep lines to a comfortable number of characters, and use CSS properties such as line-break, word-break, text-autospace and text-spacing-trim to refine the details. Load large fonts carefully with system stacks, sliced font services or subsets, and have a native reader review the result before you publish.


