浏览器如何自定义默认字体(1):默认字体、system-ui 与字体回退
浏览器如何自定义默认字体 · 第 1 篇(当前) · 2 · 3 · 4
在浏览器里改了默认字体,网页却仍然显示微软雅黑,往往与候选顺序有关。页面可以显式指定某个家族,也可以选择系统界面入口;浏览器的默认偏好只在相应的匹配或回退位置参与。
这里以 Chrome、Edge 所用的 Chromium 为主要样本,先看字体栈与系统入口,再比较 CSS 覆盖方式,并说明字体名称怎样核对。
网页通常怎样声明默认字体
字体栈是一份有顺序的候选列表。浏览器会结合字形覆盖寻找可用字体,因此,同一行里的英文、中文和 Emoji 可以来自不同字体。列表靠后的字体承担回退职责,通常不会覆盖前面已经能够绘制的字符。CSS Fonts 的匹配规则说明了这一点。
跨平台没有一份适合所有页面的唯一名单。界面希望贴近操作系统,中文正文希望保持阅读风格,代码希望保留等宽特征,这几种目标应分别配置。
跟随操作系统的界面字体
Modern Font Stacks给出的现代系统界面起点很短:
body {
font-family: system-ui, sans-serif;
}
system-ui 表示操作系统的默认界面字体,sans-serif 留作后备。它适合希望随平台呈现的应用界面,不要求 Windows、macOS、Linux 使用相同字体。
Bootstrap 5.3 的原生字体栈采用同样的系统优先思路,同时列出兼容字体与 Emoji 字体。按这个思路缩短后的示例是:
body {
font-family:
system-ui, -apple-system, "Segoe UI", Roboto,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}
-apple-system 和具体名称主要服务于兼容与后备;现代浏览器只要已经在 system-ui 处找到字形,后面的名称就不会重新决定这个字符的字体。末尾的 Emoji 候选也不是对所有符号的强制规则。
为简体中文正文明确选择字体
内容站如果希望明确选择中文阅读字体,可以把具体的中文字体放在通用字体之前。下面是一份面向简体中文正文的本地字体栈示例:
body {
font-family:
"Helvetica Neue", Arial,
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
"Noto Sans CJK SC", sans-serif;
}
前面的拉丁字体负责它们覆盖的字符,中文继续匹配苹方、微软雅黑或 Noto CJK。Linux 设备不一定安装了 Noto CJK,最后仍需要通用字体族;繁体中文内容则应结合实际语言改用对应地区的字体,并正确设置 HTML 的 lang。
这份示例表达的是网站自己的阅读偏好。如果希望把选择交给用户的浏览器设置,font-family: sans-serif 是更直接的起点。若希望多个平台接近同一种设计,则需要合法分发的 Web Font,以及字重、字符集和加载策略,单靠堆叠本地字体名不能保证一致。
代码字体另设一组
代码区域可以独立使用等宽候选:
pre, code, kbd, samp {
font-family:
ui-monospace, SFMono-Regular, Menlo, Consolas,
"Liberation Mono", monospace;
}
这和 Tailwind 的字体主题配置把不同用途拆成不同字体变量的做法一致。网站维护正文、代码、展示标题的独立入口,用户以后也更容易只覆盖自己关心的一层。
回到个人阅读偏好,这几种写法的差别就在于:浏览器的无衬线和等宽设置,分别对应 sans-serif、monospace 等入口;具体字体名和 system-ui 如果先匹配成功,后面的通用字体偏好就用不上了。设置页里的选项,需要放回这份候选列表中理解。
浏览器字体设置为什么有时没有生效
Chrome 和 Edge 可以设置标准、衬线、无衬线和等宽字体。普通字体选成汉仪旗黑、等宽字体选成 Ligconsolata,并不表示每一份网页声明都会使用它们。

| 页面入口 | 字体从哪里选择 |
|---|---|
| 标准字体及部分后续回退 | 浏览器保存的标准字体偏好 |
serif、sans-serif、monospace |
浏览器为对应类别、书写系统保存的偏好 |
"Microsoft YaHei"、"PingFang SC" |
先查找这个具体名称对应的字体 |
system-ui |
平台系统界面字体入口 |
Chrome 的字体设置接口公开了前面的通用类别,没有单独的 system-ui 配置项。标准字体会参与部分后续回退,但不会覆盖前面已经成功匹配的候选。
对于 "Inter", system-ui, sans-serif,Inter 负责自己覆盖的字符;一个没有被覆盖的汉字可以在 system-ui 处找到字形,匹配到这里就结束了。无衬线偏好位于后面的另一个入口,没有机会重新决定这个汉字的字体。
这符合当前的匹配规则,不能仅凭结果没有采用个人设置就认定为回退 bug。CSS Fonts定义的 system-ui 就是平台界面字体入口。
Windows 和 macOS 从哪里取得 system-ui
Chromium 在两个平台上采用不同的系统来源。以下结论按固定提交 5e9191cbe768e12739ef0f1d6f14155f5362fd2d 核对,具体发布版仍应检查实际绘制结果。
Windows 路径读取、缓存菜单字体,来源涉及 NONCLIENTMETRICS.lfMenuFont。本机只读查询返回 Microsoft YaHei UI。改变浏览器实际读取的菜单字体,可能影响这个系统入口,同时会影响采用该设置的其他应用,还需要验证字体度量、缓存刷新与恢复。
macOS 路径调用 Core Text 请求 kCTFontUIFontSystem 这个界面字体角色,再匹配相应样式。它没有把所有字符都固定成苹方;拉丁文字、中文和其他语言可能分别使用不同字体。字体册负责安装和管理字体,安装一份新字体不会自动重定向这个系统角色。
个人阅读偏好留在页面层,恢复时只需要让字体跟随原站,不必改动系统字体。Windows 菜单字体的系统级修改路径、macOS 的源码入口及影响范围保存在平台来源与系统级修改说明弹窗。这里没有执行系统字体写入,macOS 尚未实机验收。
两种 CSS 覆盖方式的边界
全局元素覆盖和字体名称映射都能改变阅读效果,保留的信息却不同。下面的对照来自构造页面,艺术标题和 Font Awesome 图标都有独立字体声明。

全局覆盖丢掉了原来的候选列表
最初配置先注册一个别名,再按类名排除常见图标:
@font-face {
font-family: 'sheng';
src: local('LXGW WenKai');
font-display: swap;
}
:not([class*="icon"]):not([class*="languagefont"]):not([class*="tb-ifont"]) {
font-family: sheng, 'LXGW WenKai', "HYQiHei 55S" !important;
}
类名没有命中排除词的元素,都失去了原来那份字体列表。艺术标题会一起变化,漏掉一个图标类名就可能显示错误字符;代码规则还要与正文覆盖争优先级。维护成本来自覆盖范围,继续补 :not(...) 很难解决这个根本问题。
同名资源映射保留顺序,但要知道具体名称
另一种办法是注册与目标字体同名的 @font-face,把它的 src: local(...) 指向喜欢的本地字面。页面继续请求微软雅黑,资源选择变成汉仪旗黑;前置艺术字体仍可优先匹配。这种策略适合明确的具体名称。
这份名称名单需要维护,也无法接管通用关键字。把 @font-face 的家族命名成 "system-ui",得到的是带引号的普通家族名;网页里未加引号的 system-ui 仍然使用平台入口。
CSS 可以覆盖具体名称,也可以重写某个网站提供的字体变量。它缺少的是通用操作:读取每个元素经过层叠后的候选列表,保留其中未知的独立字体,再只调整目标入口的位置。属性选择器读不到这份计算结果,CSS 变量也没有解析、插入任意字体列表的能力。
VitePress 的例子:中文为什么停在 system-ui
LiteLoaderQQNT 安装文档使用 VitePress。页面的中文正文变量包含以下顺序:

这里开头的 Punctuation SC 只承担指定标点,Inter 提供它覆盖的文字;中文可以继续匹配平台入口。一个元素使用多份实际字体,是字体回退的正常结果。
单独覆盖这个网站的 --vp-font-family-base,把个人中文字体放在 Inter 后面、系统入口前面,确实可以保留英文和标点。这样的 CSS 规则能处理已知站点,却要跟着每个网站的变量和声明维护。
难点在跨站复用。下一个网站可能把变量换名,也可能直接写在很多元素上。插件需要自动查找字体声明和它引用的变量;无法读取来源时,再根据元素的计算值转换候选列表。这样既能利用 CSS 自动匹配新节点,也保留了对特殊声明的处理能力。
字体名怎样查
扩展选目录返回的家族 ID,可以直接用于元素的候选列表;CSS 的 local() 则查找具体字面的完整名称或 PostScript 名称。设置里显示的家族名未必能用于 local()。CSS Fonts 的本地资源定义区分了这两类名称。
| 本机汉仪旗黑文件中的名称 | 对应用途 |
|---|---|
HYQiHei 55S |
家族列表,可直接作为 font-family 候选 |
HYQiHei-55S Book |
完整名称,可由 local() 查找 |
HYQiHei-EES |
PostScript 名称,也可由 local() 查找 |

插件直接保存目录 API 返回的家族 ID,通常不需要手动填写完整名称。表中的区分主要用于排查:字体文件名、家族名和具体字面的名称可以不同,不能只凭下载文件的名字判断 CSS 应该填什么。
Windows 可在「设置 → 个性化 → 字体」确认安装;macOS 可在「字体册」展开家族,选中 Regular 等具体字面,再打开「信息」查看名称。微软字体管理和字体册说明提供了相应入口。
支持 Local Font Access API的桌面浏览器,还可在可信 HTTPS 页运行 queryLocalFonts() 并授权查询。返回的 family 用于家族选择,fullName 或 postscriptName 用于 local()。这与扩展专用的 chrome.fontSettings 是不同接口;查询受支持范围和权限影响,空结果不能独自证明字体未安装。
已经安装 Node.js 时,也可下载字体名称查询脚本直接读文件:
node font-names.mjs "字体文件.ttf"
脚本读取 TTF、OTF、TTC、OTC 的 Unicode 名称记录,不改字体或系统设置。按 OpenType 名称表,id: 4 是完整名称,id: 6 是 PostScript 名称;家族、排印家族分别为 id: 1、id: 16。
名称来自具体文件,不是所有版本、平台的统一常量。新安装字体后,可以在 Popup 的字体选择面板刷新目录;若仍未出现,再确认安装范围,并重启浏览器检查。
总结
字体栈按候选顺序与字形覆盖决定实际资源,浏览器的无衬线、等宽偏好不能覆盖已经匹配成功的具体家族或 system-ui。Windows 与 macOS 的系统入口来源不同,具体文件名、家族名和字面名称也需要分别确认。
全局强制覆盖容易丢失独立字体,同名资源映射则依赖已知名称与入口。判断是否需要页面层转换时,先检查声明与 Rendered Fonts;只看到 font-family 字符串变化,还不能确认中文、英文和图标实际使用了什么字体。
下一篇:安装字体偏好扩展与管理设置。
系统字体入口、源码与系统级修改说明
源码按 2026-10-27 阅读时的固定提交核对。sans-serif 的 Blink 设置分支读取 settings.SansSerif(script);system-ui 的平台字体分支走系统数据。候选扫描后的标准字体回退不会覆盖前面的成功匹配。
Windows:菜单字体提供了一条系统级修改路径
在Windows 的字体缓存实现中,系统字体家族取菜单字体:
// chromium/src/third_party/blink/renderer/platform/fonts/win/font_cache_skia_win.cc:130-132
const AtomicString& FontCache::SystemFontFamily() {
return MenuFontFamily();
}
继续向上追,gfx::win::SystemFonts通过 SPI_GETNONCLIENTMETRICS 读取系统参数,将 lfMenuFont 作为 SystemFont::kMenu 保存。渲染视图的偏好初始化再把菜单字体家族和字号写入渲染器偏好,随后传入 Blink,通过 SetMenuFontMetrics 缓存。
gfx 还会进行字号缩放等调整;非空的 font_family_override 也可以覆盖读出的字体名。本地化字体调整函数就提供了这种覆盖。因此,系统参数中的名称不能直接证明某个浏览器最终绘制了哪份字体,仍要检查实际结果。字体家族与网页自己的 CSS 字号也是不同参数,修改菜单字体不等于统一改变网页字号。
即使菜单字体名也是 Microsoft YaHei UI,这条路径仍然直接创建平台字体数据,不会重新经过网页的 @font-face 名称匹配。因此,给这个名称注册 CSS 别名,也不能接管 system-ui。
Windows 的 NONCLIENTMETRICSW结构包含 lfMenuFont,用于菜单栏字体;系统同时提供 SPI_GETNONCLIENTMETRICS 和 SPI_SETNONCLIENTMETRICS 读取、设置这组参数。SystemParametersInfoW还允许保存用户配置并广播设置变更通知。
本机只读查询得到的菜单字体是 Microsoft YaHei UI,常规字重为 400。这是当前 Windows 环境的取样,不是所有语言版本的统一默认值,也没有据此修改系统设置。
从上述链路看,改变浏览器实际读取的菜单字体,可以影响这一版 Chromium 的 system-ui 入口,从而覆盖一部分原本需要页面脚本处理的情况。但它只改变跟随这个入口的文字,网页显式指定的微软雅黑、品牌字体或艺术字体仍有各自的匹配路径。
系统级修改也会扩大影响范围:
- 采用系统菜单字体的其他应用会跟着变化。自绘界面和自行指定字体的应用可能保留原样,因此不能期待整个 Windows 都呈现一致的变化。
- 新字体的字宽、升部、降部和字形覆盖可能不同。菜单高度、布局或应用自己的裁剪假设未必随之调整,需要检查截字、缺字和符号回退。
- Chromium 的这组系统字体存在进程内缓存。API 的变更通知不等于缓存已经刷新,应退出包括后台进程在内的目标浏览器,再启动并检查实际绘制结果。
- 恢复时需要保留原来的字体及相关度量,不能只记住一个字体名字。只想修改网页字体时,系统范围通常大于实际需求。
这条推导针对的是实际的菜单字体配置。只添加字体名称替换、重命名字体文件,或安装一个新字体,都不能证明 lfMenuFont 已经改变。若使用字体调整工具,应先确认它修改哪一层,再读取菜单字体并检查浏览器的 Rendered Fonts。
本次完成了源码追踪和菜单字体的只读查询,没有做系统级写入及恢复实验。因此这里给出可行路径和验收条件,不把它写成已经在所有 Chrome、Edge 版本验证通过的一键方案。
macOS:Core Text 选择系统界面字体角色
Chromium 的macOS 字体创建分支在遇到 system-ui 时调用 MatchSystemUIFont。这个函数的初始字体请求是:
// chromium/src/third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.mm:647-648
ScopedCFTypeRef<CTFontRef> ct_font(
CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, size, nullptr));
这里请求的是系统界面字体角色,后续再匹配字重、斜体和宽度。它没有把 PingFang SC 写死成统一的字体名称。
Apple 将 San Francisco 家族中的 SF Pro 作为平台系统字体,覆盖拉丁、希腊和西里尔等书写系统。Apple 的字体说明
中文等字符还可能使用其他语言字体,具体选择受系统版本、语言和字形覆盖影响。CSS 对系统字体组合的说明
字体册负责安装、验证和管理字体。它的公开设置没有一个用于重定向所有应用系统界面字体的家族选择器;部分应用可以在自己的设置里选择默认字体。把霞鹜新晰黑安装进字体册,会让它成为可用字体,不会自动改写 Core Text 的系统界面角色。
因此,Windows 菜单字体的做法不能原样移植到 macOS。本文对 macOS 的结论来自源码和官方资料,尚未做 macOS 实机绘制及系统修改实验。若目标仅是浏览器内的个人阅读偏好,继续在浏览器或页面层配置,更容易控制影响范围。