Writing · 2026
Apple's Chinese site does not look designed. That is the achievement. The markup is ordinary HTML, the CSS is ordinary CSS, and there is no effect here you could not write yourself. What is unusual is that the stylesheets are full of small corrections, and every one of them exists because somebody rendered a page, looked at it, and did not like what they saw. A recent write-up collects a good number of them (SkyWT). This one goes back to the stylesheets to measure each correction and occasionally disagree.
The reason any of this is needed is that Chinese and Latin type are two different metric systems, and the browser applies one set of rules to both. A Han character is drawn to fill its em box — the square is the design. A Latin capital is drawn to sit inside a much smaller zone within the same box, with sidebearings either side and ascender space above that it rarely uses. Set both at one font-size and they do not look the same size, because they are not: font-size is a box, and the eye compares ink.
Everything below is a discrepancy of that kind and the fix Apple applies to it. Each figure measures the effect live in your browser, because the effect depends on which fonts you have — which is also why these fixes are done by hand rather than compiled in.
Start with size. In most CJK fonts the ink of a Han character covers around nine tenths of the em; in a Latin text face the cap height is nearer seven tenths. At one font-size the Han is roughly a third taller than the capital beside it.
Apple's fix is a private web font, SF Pro SC, loaded from /wss/fonts and sitting first in the stack (Apple). It is worth opening the file, because the naming is misleading in a useful way: the family is called SF Pro SC, the PostScript name is SFProPingFangSC-Regular, and the file it loads from is PingFangSC-Regular.woff2. It is PingFang SC, reprocessed (GitHub).
Two numbers come out of that comparison. The Han glyphs are scaled to 0.887 em from 1.0, and the outlines are scaled about their centre rather than shifted, so it is the drawing that is smaller and not just the advance. Meanwhile the network version has been stripped of its GPOS table entirely: it ships vert and vrt2 for vertical text and nothing else. No kerning, no optical tracking table, no palt. That absence is why the punctuation adjustment in the next section had to be baked into the glyph widths instead of expressed as spacing.
The direction of the correction is not fixed. Source Han Serif takes the opposite route: its Latin comes from Source Serif and is scaled up by about a fifth so it can hold its own against the Han. Same goal, opposite adjustment. The choice follows whatever dominates the page — mostly Chinese, shrink the Chinese; mostly Latin, grow the Latin. Splitting the difference gets you the awkward middle: at 64px, the word iPhone set in plain Latin beside Han has to be pushed to about 77px before it stops looking small, and by then it looks heavy, so weight and optical size have to be tuned too. One correction is cheap; two fighting each other is not.
One more thing falls out of reading the font rather than the CSS: the network file carries only about 4,600 Han characters, so anything rarer falls through to the system PingFang in the stack — at 1.0em. A rare character sitting in a sentence full of common ones will render about thirteen percent larger than its neighbours, and no amount of CSS will fix it.
Now the punctuation. A full-width comma is one em wide, and its ink sits in one corner of that em: , is a small mark in the lower left of a full square, and 。 is a circle in the same corner. So the mark charges a full em of layout and shows you about half of one, leaving the rest empty.
在 Apple 购买 Mac,好处多多。
Apple's adjustment is to narrow the mark, and the measurement shows exactly what was done. The comma's advance goes from 1.0 em to 0.507 em; the full stop to 0.552; the opening and closing book-title marks to 0.662. Crucially the left side bearing barely moves — 119 to 125 units — while the right side bearing is cut from 739 to 256. Apple did not scale the mark; it deleted the empty right half of the character box. The mark keeps its position and its ink, and stops charging for the space it was not using.
/* 1 · 缩放标点,并把多出来的宽度还回去 */.ty-punct { display: inline-block; transform: scaleX(.507); /* 逗号的实测宽度 */ transform-origin: 0 50%; margin-right: calc(-.493em); /* (1 - .507) × 字宽 */}/* 2 · 让标点完全不参与排版宽度 */.punct-absolute { position: absolute; } /* Apple: .l-p / .m-p / .s-p *//* 3 · 只把右边多出的那一份收回来 */.punct-pull { margin-right: -.4em; } /* Apple: .hide-symbol */In body text at 15px this is invisible. In a slogan at 30px the empty half becomes a hole, and the sentence reads as two fragments separated by a gap wider than any word space inside either half. The demo above reports both gaps in pixels: the middle one between the two phrases, and the trailing one after the last mark.
This is also why you cannot find the rule by searching the page. There is no CSS narrowing punctuation anywhere on the site; the glyph is narrower. Narrowing it in CSS instead is what the figure does, and it is a reasonable approximation — but it has to be undone for print and for copy-paste, which is the price of doing at the display layer something that belongs in the font.
Chinese has no word spaces, so the browser may break between any two Han characters, subject to the prohibition on starting a line with closing punctuation. That is usually right and occasionally very wrong: a two-character word that happens to land on the edge gets cut in half. Apple's protection is one utility class, identical on every page, that turns the default off for one word at a time.
中文与西文的度量方式并不相同,浏览器却只按一套规则摆放文字,所以每当中英文混排,就得有人把每一行重新看过一遍,再决定这一行在哪里断开,以及哪个词不该被拆开。
想升级,
时机正正好。
<p>……再决定这一行在哪里断开<span class="nowrap">句子。</span></p>/* 每张页面的基础样式表里都只有这么一条,一模一样 */.nowrap { display: inline-block; text-decoration: inherit; white-space: nowrap }/* 只在窄屏生效的换行,每页的 built CSS 里各定义一次 */br.large { display: block }@media (max-width: 1068px) { br.large { display: none } }@media (max-width: 734px) { br.large { display: none } }br.medium { display: none }@media (max-width: 1068px) { br.medium { display: block } }@media (max-width: 734px) { br.medium { display: none } }br.small { display: none }@media (max-width: 734px) { br.small { display: block } }There are 535 of these spans across the Chinese pages, and they are not all doing the same job. Some stop an orphan, which is the obvious use. Some hold a number to its unit, so that 最高达 RMB 1350 does not split across a line. Forty-six of them exist to pin a footnote superscript to the word it annotates — the mark cannot be allowed to wrap away from its referent. One definition serves all four jobs.
There is a second mechanism at the same level, and it leaves no trace you can search for unless you know to look for it. Apple also uses an invisible character, U+2060 WORD JOINER, inserted between the characters of a two-character word: 疑问, 选购, 专家, 分期. It does the same job as the span with no markup at all, which is why a page can be full of protected words and have very few nowrap spans in the source. The English site takes this to an extreme, putting a joiner between every character of a locked-up phrase; the Chinese site puts one at the word boundary only, because Chinese can break anywhere and English cannot break inside a word.
The other half of break control is the opposite instinct: rather than let the browser choose and then patch the result, insert the break yourself. Apple does it with breakpoint-scoped <br> elements, one class per container width, and the three are mutually exclusive — br.large shows only above 1069px, br.medium only from 735 to 1068, br.small only below 735. The two lines of 「想升级,/时机正正好。」are not a wrap. They are markup, and on a wide screen they are one line again.
Which raises the question of where the breaks are chosen. The container widths are fixed and few — 980px large, 692px medium, 87.5% small — and the write-up argues that the education page's five protected words each correspond to a break that actually happens at exactly one of those widths and nowhere else (SkyWT) (apple.com.cn). Protecting 伙伴 at 980px also pushes the break far enough that 建立 lands somewhere safe, which is why 建立 needs no span of its own. That is the signature of a value found by looking rather than by computing: the set is minimal but irregular, and it only makes sense once you know the three widths.
You do not have to take that on faith, because the mechanism is checkable in a browser with one API. A Range over a text node returns one client rectangle per line box (MDN), so line count, each line's width, and the fill of the last line are all directly readable — and a range over a single protected word returns more than one rectangle the moment that word has been split. Turn protection off, sweep the column, and the words that break light up.
Back to the full stop. Centre a line that ends in one and the geometry and the eye disagree. text-align: center centres the advance width of the line; the eye centres the ink. Because the last mark's ink sits in the lower left of its em, the ink centre lands left of the box centre, and the amount is computable: if the mark's ink ends at r within an em of width e, the shift is (r − e) / 2. For a full stop that is roughly a third of an em — and the narrower marks from the previous section halve it, which is why the correction on the page is 0.3 to 0.6em rather than 1em.
支付,由 Apple 支招。
What Apple does about it is more interesting than any single rule, because of how many rules there are. The Chinese stylesheet carries a whole library of centring corrections (apple.com.cn). Some are named after their value, some are ladders you pick from, and several apply only inside one breakpoint, which means on a wide screen they do nothing at all.
| class | padding-left | range |
|---|---|---|
.centerall | 0.4em | all widths |
.centerall_05 | 0.5em | all widths |
.centerall_055 | 0.55em | all widths |
.centerall_06 | 0.6em | all widths |
.centeralign | 0.4em → 0 | ≥736px |
.centerall_43 | 0.4 → 0.3em | ≤735px |
.centeralign_06 | 0.6em → 0 | ≥736px |
.centerexs | 0.3 → 0.15em | ≤320px |
.centerms | 0.4em | ≤1068px |
.center_s7 | 0.65em | ≤735px |
.center_s4 | 0.4em | ≤735px |
.cl_00 … .cl_100 | 0 – 1em, by 0.05 | all widths |
.cm_00 … .cm_100 | 0 – 1em, by 0.05 | ≤1068px |
.cs_00 … .cs_100 | 0 – 1em, by 0.05 | ≤734px |
.hide-symbol .l0 … .l10 | 0 → −1em, by 0.1 | all widths |
A single padding value would have been the obvious engineering answer. What is there instead is two ladders and a set of hand-named constants. The ladders are the tell: .cl_00 through .cl_100 is twenty-one values of padding-left from 0 to 1em in steps of 0.05, repeated for three breakpoints — sixty-three rules that exist because the amount genuinely varies per heading, per width, per font size, and there is no formula. In the HTML the three are written together, cl_06 cm_06 cs_00, which is an author saying: six hundredths of an em on large, the same on medium, none on small.
The hand-named constants say the same thing in a more human way: _05, _055, _06, _43 are decimals, not meanings. So is .centerms, which is wrapped in a media query with no base rule at all — somebody found the correction was needed on small and medium and not on large, and wrote down exactly that. And so, most of all, is .hide-symbol: a class whose entire purpose is a negative right margin, with ten numbered steps from .l0 to .l10 going from zero to a full em, each one repeated per breakpoint. Nobody designs a ten-stop negative margin ladder in advance. You add a step when a heading needs it.
/* 方案一:给整个盒子一块左边的补白(Apple 的主力方案) */html[lang=zh-CN] .centerall_05 { padding-left: .5em; }html[lang=zh-CN] .centerall_06 { padding-left: .6em; }/* 同一件事的完整阶梯:三个断点 × 21 档 = 63 条规则 */html[lang=zh-CN] .cl_00 { padding-left: 0em; }html[lang=zh-CN] .cl_01 { padding-left: .1em; }html[lang=zh-CN] .cl_015{ padding-left: .15em; }/* … 直到 … */html[lang=zh-CN] .cl_100{ padding-left: 1em; }/* HTML 里三个类一起写,一次覆盖三个断点 *//* <h2 class="default-headline cl_06 cm_06 cs_00"> *//* 方案二:负外边距,按幅度分十档 */.hide-symbol { margin-right: -.4em; }.hide-symbol.l1 { margin-right: -.1em; }.hide-symbol.l6 { margin-right: -.6em; }.hide-symbol.l10{ margin-right: -1em; }/* 方案三:让标点不占宽度 */.l-p { position: absolute; }Which of the three designs gets used on a given page depends on what else the line has to do. An absolutely positioned span is the cleanest and keeps the punctuation out of the layout entirely; it is the choice when the mark sits at the end of a line that will not move. Note that the classes are structured by breakpoint rather than by mark size: -p means position absolute and -rp means revert to static, while l-, m- and s- say which width range the instruction applies to. So .s-p is not “small punctuation”; it is “ignore this mark's width only below 735px”, which is what you want when a headline breaks differently on a phone. A negative margin is the lightest and keeps the mark in the flow so it can still be selected. A padding-left on the block is the fallback when the element has other behaviour attached — the privacy page's heading takes that route because it has a character-reveal effect riding on it (apple.com.cn).
This is the correction the write-ups tend to miss, and it is the largest one on the site. Latin typography is built on negative tracking: Apple's own headline styles run from -.015em at 96px down to about -.003em at small sizes, and body copy sits at -.022em. Negative tracking pulls letters closer, which is exactly what large Latin type needs and exactly what Chinese cannot survive: Han characters are squares with ink running to the edge of the box, and pulling the boxes together makes the strokes touch (apple.com.cn).
字距这个东西是给字母设计的
The fix is one rule per typographic class with a :lang(zh) suffix that sets letter-spacing to zero. Across the stylesheets that is 1,151 declarations. Apple does not special-case a few headings; it turns the whole Latin tracking system off for Chinese, class by class, and the ones it forgets are exactly where you will see text that looks cramped.
/* 西文的排版节奏建立在负字距上,字号越大负得越多 */.typography-headline-standalone { font-size: 96px; letter-spacing: -.015em; }.typography-body { letter-spacing: -.022em; }/* 中文站用一条 :lang(zh) 全线清零 —— 全站 1151 处 */.t-headline-standalone:lang(zh) { letter-spacing: 0 }.t-headline-super:lang(zh) { line-height: 1.0875; letter-spacing: 0 }.t-headline:lang(zh) { line-height: 1.21875; letter-spacing: 0 }.t-body:lang(zh) { letter-spacing: 0 }.t-caption:lang(zh) { letter-spacing: 0 }/* 行高则上调,字号越小加得越多 *//* 西文 中文 差 *//* 80px 1.05 1.0875 +0.0375 *//* 48px 1.0835 1.21875 +0.135 *//* 24px 1.1667 1.29167 +0.125 *//* 正文段落再单独加一档,只给简体中文 */html[lang=zh-CN] .main .body-copy { line-height: 1.8; }Line height goes the other way, which is the subtler half. Chinese paragraphs need more leading than Latin ones, and the smaller the type the more they need: at 80px the Chinese line height is about four percent above the Latin value, at 48px it is thirteen, at 24px twelve. The pattern is that the gap widens as the type shrinks. Simplified Chinese then gets two more rules of its own — line-height: 1.6 for introductions and elevated copy, and 1.8 for body paragraphs, the highest value anywhere in the stylesheets. The traditional Chinese site does not have them, which is either a difference of judgement between the two teams or a rule that has not been synced yet.
While you are in there: the root font size on the Chinese site is 106.25%, which is 17px rather than the browser default 16. That is why font-size: 17px appears 415 times, more than any other value. And the weight system is unusually narrow — Semibold accounts for 1,543 declarations and Regular for 943, while Bold appears 37 times in total, and the font itself is only served in four weights, 300 through 600. The page is not built out of many weights. It is built out of two.
Han and Latin next to each other need air. W3C's Chinese layout requirements put a number on it: no more than a quarter of a Han width, and none at all when the Latin or the digit falls at the start or end of a line (W3C). Professional typesetting software has inserted this automatically for decades, and Apple's own text engines do it — type 中文text in Notes and watch. The web did not, which left every author to solve it by hand.
拖动这个滑块Apple会把汉字与西文之间的空距加回来,Safari也一样。
Apple's solution is the bluntest one available: a person typed a space. Across the Chinese pages there are roughly 8,500 places where a Han character is followed by an ASCII space and then a Latin letter or digit, against 36 uses of for the same purpose. There is no text-autospace anywhere, no word-spacing rule, and no script that inserts the gap at runtime (MDN). Just spaces, in the copy.
You can tell it is manual because it is occasionally wrong: a page that shipped fast has one missing, or one too many, and the defect has the exact shape of a person forgetting. The property that would cover all of it exists now, and Apple has not switched to it — a reasonable choice given how much of the copy has already been hand-corrected, and a reminder that a manual pass becomes load-bearing the moment you build enough of it.
The most instructive fixes in the stylesheet are the ones that are not about the text at all. They are about what happens to everything else when the text changes writing system, and they are easy to miss because they look like rounding errors (apple.com.cn).
/* 图标也要跟着中文修正 */html[lang=zh-CN] .more::after,html[lang=zh-CN] .more .icon-htc,html[lang=zh-CN] .icon-downloadsolid::before,html[lang=zh-CN] .icon-chevronright::before,html[lang=zh-CN] .icon-chevronright::after { position: relative; top: -1.5px; }html[lang=zh-CN] .icon-play::before,html[lang=zh-CN] .icon-play::after { top: -1px; }/* 角标:正文里 .8em,标题里必须收到 .5em */html[lang=zh-CN] sup.star { font-size: .8em; }html[lang=zh-CN] h1 sup.star,html[lang=zh-CN] h2 sup.star,html[lang=zh-CN] h3 sup.star { font-size: .5em; }The icons get nudged. Every chevron, play triangle and download glyph on the Chinese site is pushed up by one to one and a half pixels relative to the English one, because CJK fonts distribute their ascent and descent differently and an icon aligned to the baseline sits slightly low beside Han. Nobody sees the fix; everybody sees the version without it, as an icon that never quite lines up. The superscript asterisk is .8em in body text and .5em inside a heading, because at 40px a .8em asterisk is 32px — the size of your body copy — and the footnote marker starts reading as part of the headline. Some corrections are scoped to a width range with no base rule at all, which means the value is not a constant that happens to be overridden but a fact about that range alone.
There is a whole second system of this kind for accessibility. When the reader has enlarged text, 246 rules switch on: every nowrap is released back to normal wrapping, hyphenation and overflow-wrap are enabled, and even the non-breaking spaces are downgraded so they can break, via -webkit-nbsp-mode: space. The locked-up copy that makes a headline break well at its intended size is precisely what would overflow at twice the size, so it has to be undone. If you hand-control line breaking, you are also signing up to un-control it.
Almost every fix in this article has a property that now does it, or does part of it. The mapping is worth keeping, because the browser-side ones are free and the hand ones are not.
| what the hand fix does | what does it now | notes |
|---|---|---|
| protect a word from breaking | white-space: nowrap | Or U+2060 between the characters. Still hand work either way. |
| keep a product name whole | | Apple writes iPhone 18 Pro by hand. |
| trim the empty half of a full-width mark | text-spacing-trim | trim-both handles the pair, space-first is the safe default. |
| put space between Han and Latin | text-autospace | Chromium only. Apple has not switched to it. |
| avoid an orphan | text-wrap: pretty | Chromium 117+. It rewraps the whole paragraph. |
| balance a headline | text-wrap: balance | Apple does not use it. This site does, on every project row. |
| a CJK line height | :lang(zh) + line-height | No shorthand. It is one selector per typographic class. |
| centre on the ink | — | No property exists. A padding, a negative margin, or an absolute span. |
The two that carry the most weight are text-spacing-trim and text-autospace (MDN) (MDN), because between them they cover the punctuation narrowing and the script gap — the two things Apple solves deepest in the font and the copy respectively. Both are Chromium-only for now, which is why a page that wants to look right everywhere still needs the manual version underneath. Note that neither replaces the centring correction. There is no property for centring on the ink, because the browser has no notion of where the ink is.
// 一行在哪里结束:一个 Range 就是一行const range = document.createRange();range.selectNodeContents(paragraph);const lines = [...range.getClientRects()]; // 每个行盒一个矩形const lastLine = lines.at(-1);const fill = lastLine.width / lines[0].width; // 末行填充率// 一个字有没有被拆到两行:改看它自己的矩形const word = document.createRange();word.selectNodeContents(protectedSpan);const split = word.getClientRects().length > 1;// 墨迹有多宽:measureText 给的是墨迹,不是字宽const ctx = document.createElement('canvas').getContext('2d');ctx.font = '600 27px -apple-system, "PingFang SC", sans-serif';const m = ctx.measureText('支付,由 Apple 支招。');const ink = m.actualBoundingBoxLeft + m.actualBoundingBoxRight;const boxW = m.width;const shift = (m.actualBoundingBoxRight - m.actualBoundingBoxLeft - boxW) / 2;// shift 为负:墨迹中心在盒子中心左边这么多像素Which brings back the question of how you find any of this in the first place. The answer is that you do not have to notice it — you have to measure it. Two APIs cover most of the ground. A Range over a text node reports one rectangle per line box, which gives line counts, line widths, the fill of the last line, and whether a protected word got split, at any container width, in a loop (MDN). measureText reports ink extents rather than advance widths, via actualBoundingBoxLeft and actualBoundingBoxRight, which is the difference between the box and the thing the eye sees (MDN). Every figure on this page is built from those two calls, plus a zero-size inline-block probe whose bottom edge is the baseline.
The through-line is the same one that runs under Apple's material design work: as much of the decision as possible is moved out of the browser's defaults and into a measurement somebody made. The difference is that glass announces itself and type does not. A reader will never tell you the comma is too wide, or the asterisk in the headline is too large, or the chevron sits a pixel and a half low. They will just find the page slightly easier to read than the one next to it, and not know why. That is the whole return on the work, and it is invisible in a design review — only in a rendering.