跳到主要内容

字体

仅用系统字体栈 —— iOS 走 -apple-system,Android 走系统默认,CJK 走 PingFang SC / Microsoft YaHei。不引入 webfont:品牌刻意依赖 OS 渲染原生中文字形。字号 / 字重的真相源是 src/theme/tokens.ts

字号会随屏宽缩放

type 的每个值经 rf() 做 moderate 缩放(系数 0.3,对中文字号最友好;基准宽 402pt = iPhone 17 Pro)。下表数值是基准设计值,真机上略有浮动。详见 src/theme/scale.ts

字体栈

系统栈在 RN 端由 OS 默认提供;文档站 / web 侧 CSS 栈:

font-family:
-apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;

等宽栈走 fontMono token(iOS Menlo / Android monospace,用于数字、代码风格元数据):

import { fontMono } from '@unif/react-native-design';
// style={{ fontFamily: fontMono }}

字号阶梯 / type

Token基准 px用途
display22品牌标题
h118页面标题
h217区块标题
h315卡片标题 / 强调正文
body15默认正文
sm14次级正文 / 按钮
xs13提示、说明
xxs12meta、caption
micro11徽章、工具名
nano10TabBar / Grid / Citation / Sources 角标、最小标签

半档微调

为收敛"近似 token + delta"反模式,补了几个 0.5 step 的半档,各有固定语义:

Token基准 px用途
smPlus14.5dashboard card 标题("小一档 semi"惯例)
xsPlus13.5长文阅读字号(Privacy 正文)
microPlus11.5VersionPill / profileCard / Splash / carousel 副标题

Hero 档

Hero 区主标题的三档(带 Logo / NavBar 的品牌大标题):

Token基准 px用途
heroLg26一级 brand hero(带 Logo,Login 屏)
heroMd22二级 hero(独立屏 + NavBar,ForgotPassword)
heroSm18承接式 hero(Group 选组 / 选角色子区)

字重 / fw

Token别名
fw.regular'400'regular
fw.medium'500'medium
fw.semi'600'semi
fw.bold'700'bold
fw.heavy'800'heavy

fwas const 声明,类型已收窄到字面量字符串,不需要再写 as '500' / as '600' 这种 cast

行高(惯例,非 token)

行高没有独立 token

设计系统没有 lineHeight token。行高按惯例用字号乘系数算:标题 ×1.25(紧凑)、正文 ×1.45。在 makeStyles 里直接写 lineHeight: t.body * 1.45 即可,不要去 import 不存在的 lh token。

惯例系数用途
紧凑×1.25标题
正文×1.45正文段落(约 22 / 15)

在代码中使用

import { StyleSheet } from 'react-native';
import {
type as t,
fw,
fontMono,
useThemedStyles,
} from '@unif/react-native-design';
import type { ColorTokens } from '@unif/react-native-design';

const makeStyles = (c: ColorTokens) =>
StyleSheet.create({
title: {
fontSize: t.h2, // 17
lineHeight: t.h2 * 1.25, // 标题用 ×1.25
fontWeight: fw.semi, // '600'(无需 cast)
color: c.foreground,
},
body: {
fontSize: t.body, // 15
lineHeight: t.body * 1.45, // 正文用 ×1.45
color: c.foreground,
},
meta: {
fontSize: t.micro, // 11
fontWeight: fw.semi,
color: c.foregroundSubtle,
},
code: {
fontFamily: fontMono,
fontSize: t.xxs, // 12
},
});

function Article() {
const styles = useThemedStyles(makeStyles);
// ...
}

type 导入时常起别名 t(避开 JS 保留字 type):import { type as t } from '@unif/react-native-design'

应用级字号缩放(fontScale)

app 内「字体大小」档位经 <ThemeProvider fontScale={…}> 传入(默认 1),useThemedStyles 出口对 maker 产物的 fontSize / lineHeight / letterSpacing 统一缩放:

  • 只接受有限正数,不设人为上限;0、负数、NaNInfinity 和非数字值均回退为 1;
  • 精确只缩放 fontSizelineHeightletterSpacing;Icon、Spinner、spacing、控件尺寸、圆角和 fixed.* 均不缩放;
  • fontScale = 1 时恒等返回原引用,未接入的消费方(web 文档站等)行为与引用完全不变;
  • rf() 正交:rf() 是按屏宽的静态适配(模块加载时算一次),fontScale运行期用户档位(变更即触发全树 themed 样式重算)。

接入方自持档位状态(persist store 等)。useThemedStyles 只处理 maker 产物;render 期间另外拼入的动态文字 metric 必须显式缩放:

import {
normalizeFontScale,
scaleFontMetric,
useFontScale,
} from '@unif/react-native-design';

const savedScale = normalizeFontScale(settings.fontScale);

function DynamicLabel({ size }: { size: number }) {
const fontScale = useFontScale();
return <Text style={{ fontSize: scaleFontMetric(size, fontScale) }} />;
}

normalizeFontScale(value)scaleFontMetric(value, factor) 都是无副作用纯函数;后者会先归一化 factor,再且仅再乘一次。不要把已经由 useThemedStyles 缩放过的值再次传给 scaleFontMetric

库内 render-time dynamic typography 已按同一规则接线:Button、Avatar、Segmented、Stepper 中央值、Tag 与 AvatarWithRing 使用原始字号调用 scaleFontMetric 一次;Button Icon、Avatar / AvatarWithRing 容器、Segmented track、Stepper visual 与 outer(md/sm 至少 44pt;xs 高 44pt、横向小于 44pt)、Tag 高度均保留原尺寸。其余 styles.ts maker 产物只由 useThemedStyles 统一处理,不再在组件调用点重复缩放。

缺少 ThemeProvider 时,主题 hook 返回同一个模块级 light fallback(lightColors / lightShadow / fontScale=1),保证引用稳定。开发环境会在 React effect 阶段一次性记录诊断;render 期间不会写日志。