- 前端
- UI组件
- 设计系统
【免费下载链接】theme-ui
Build consistent, themeable React apps based on constraint-based design principles
本指南围绕 Theme UI 内置预设
@theme-ui/preset-funk展开,讲解如何安装引入该预设、其源码层面的组成结构(基于 preset-base 的字体/行高/字重/颜色覆盖),以及如何在ThemeProvider、ThemeUIProvider与gatsby-plugin-theme-ui中把它作为示例或起点,扩展出你自己的主题。读完本文,你将能直接安装并改造 funk 预设,理解 Theme UI 预设"分层叠加、按需覆盖"的实现原理。
一、funk 预设是什么
Theme UI 是一个基于约束设计原则(constraint-based design principles)构建可主题化 React 应用的设计系统。为了让开发者快速上手,官方在packages/目录下维护了一系列现成主题预设,funk就是其中之一。
根据 packages/preset-funk/README.md 的说明,@theme-ui/preset-funk可以被当作示例或起点来使用——也就是说,它的价值不只是开箱即用的视觉主题,更是"演示一个 Theme UI 预设应该怎么写、怎么被扩展"的模板。
从仓库结构看,funk 预设的全部实现都集中在 packages/preset-funk/src/index.ts 一个文件里(约 30 行),它没有自己从零定义全套主题量表,而是基于@theme-ui/preset-base做增量覆盖。这种"基础预设 + 局部覆盖"的组合方式,正是 Theme UI 预设体系的核心组织思路。
二、安装与引入
2.1 单独安装 preset-funk
在 React 项目中安装该预设:
npm i @theme-ui/preset-funk引入并使用:
// example theme import theme from '@theme-ui/preset-funk' export default { ...theme, styles: { ...theme, }, }注意上面示例中styles字段的写法沿用了原 README 的写法(把整个 theme 展开进styles,这一写法与 packages/presets/README.md 中...funk.styles的写法略有差异,实际使用时建议按后者的语义展开theme.styles)。更符合 Theme UI 语义、也更稳妥的写法是:
import { funk } from '@theme-ui/presets' export default { ...funk, styles: { ...funk.styles, }, }2.2 通过聚合包 @theme-ui/presets 引入
除了单独安装,Theme UI 还提供了聚合预设包@theme-ui/presets。从 packages/presets/src/index.ts 可以看到,funk与其他内置预设(base、dark、deep、future、roboto、swiss、system、tosh、bootstrap、bulma、polaris、tailwind、sketchy)一起被集中导出:
export { default as funk } from '@theme-ui/preset-funk'安装方式:
npm i @theme-ui/presets两种引入方式最终拿到的是同一个主题对象:单个包以export default导出,聚合包则提供命名导出funk。根据 packages/presets/test/index.tsx 中的测试,funk与所有内置预设一样被断言为合法的Theme类型,可以直接传给ThemeProvider。
三、源码级解析:funk 的主题组成
funk 预设的完整源码如下(packages/preset-funk/src/index.ts):
import base from '@theme-ui/preset-base' import { makeTheme } from '@theme-ui/css/utils' export const funk = makeTheme({ ...base, fonts: { body: 'Poppins, sans-serif', heading: 'Poppins, sans-serif', monospace: 'Menlo, monospace', }, lineHeights: { body: 1.625, heading: 1.25, }, fontWeights: { body: 400, heading: 900, bold: 700, }, colors: { ...base.colors, primary: '#609', secondary: '#306', }, styles: { ...base.styles, }, }) export default funk可以看到 funk 的主题对象可以拆成两层:
3.1 第一层:继承自 preset-base 的量表
...base展开后,funk 天然继承了 packages/preset-base/src/index.ts 定义的整套基础量表:
- space 间距刻度:
[0, 4, 8, 16, 32, 64, 128, 256, 512],配合sx/ styled-system 风格属性使用; - fontSizes 字号刻度:
[12, 14, 16, 20, 24, 32, 48, 64, 96],即h1~h6分别引用下标5~0; - colors 颜色表:
text: '#000'、background: '#fff'、primary: '#07c'、secondary: '#30c'、muted: '#f6f6f6'; - styles 元素样式集:
root(设置fontFamily/lineHeight/fontWeight为body)、h1~h6(统一走heading字体变量)、p、a(颜色为primary)、pre/code(等宽字体)、table/th/td、img(maxWidth: '100%')等。
正因为有这些继承来的量表,funk 才能在只覆盖几个字段的情况下具备完整可用的排版与颜色体系。
3.2 第二层:funk 自己的覆盖点
funk 在原基础上只覆盖了四类字段,这也构成了它的视觉个性:
| 覆盖字段 | funk 值 | 相对 base 的变化 | 影响范围 |
|---|---|---|---|
fonts.body/fonts.heading | 'Poppins, sans-serif' | 由系统字体栈换成 Poppins | 正文与标题字族 |
fonts.monospace | 'Menlo, monospace' | 保持不变 | 代码等宽字体 |
lineHeights.body | 1.625 | 由1.5抬高 | 正文行距更宽松 |
lineHeights.heading | 1.25 | 由1.125抬高 | 标题行距 |
fontWeights.heading | 900 | 由700加粗 | 标题字重更醒目 |
colors.primary | #609 | 由#07c换成紫色系 | 链接、按钮等主色 |
colors.secondary | #306 | 由#30c加深 | 辅助强调色 |
这种写法直接体现了 Theme UI 主题规范(可参考 packages/docs/src/pages/theme-spec.mdx):主题对象由Scales(量表)、Variants(变体)、Styles(MDX 内容样式)三类数据组成,预设之间可以互相叠加。
3.3 关于 makeTheme 的类型约束
funk 的styles、colors等字段在展开时都保留了字面量的精确类型,而不是被拓宽成宽泛对象,这归功于makeTheme。它定义在 packages/css/src/utils.ts:
export const makeTheme = <T extends Theme>(theme: T): T => theme这是一个"受约束的恒等函数":运行时不做任何转换、原样返回主题对象,但在类型层面要求传入对象满足Theme结构,同时保留其精确的推断类型(preserve its exact type)。因此funk既能通过 Theme UI 的类型检查,又能在 IDE 中获得完整的字段提示。这也是在编写自定义预设时推荐使用makeTheme的原因。
四、基于 funk 扩展你自己的主题
README 的核心意图就是"把 funk 当作示例或起点来扩展自己的主题"。扩展的最小套路是:先展开预设,再覆盖需要的字段。
4.1 覆盖量表字段
比如想把主色调换回蓝色系,并增加自定义sizes:
import theme from '@theme-ui/preset-funk' const myTheme = { ...theme, colors: { ...theme.colors, primary: '#07c', accent: '#ff0', }, sizes: { container: 768, }, }由于先展开了...theme,未覆盖的量表(space、fontSizes、lineHeights等)全部保留,primary的引用(如theme.styles.a中color: 'primary')会自动生效。
4.2 覆盖 styles 内容样式
funk 的styles与 base 一致,若想让a标签在悬停时变色、让pre代码块带背景,可以这样扩展:
const myTheme = { ...theme, styles: { ...theme.styles, a: { ...theme.styles.a, '&:hover': { color: 'accent', }, }, pre: { ...theme.styles.pre, p: 3, bg: 'muted', }, }, }样式对象里的color: 'primary'、bg: 'muted'这类字符串值会在运行时按主题量表解析成真实颜色(Theme类型中styles字段的定义可参考 packages/css/src/types.ts),因此扩展时沿用主题令牌而不是硬编码色值,能保证深浅色替换时依然自洽。
4.3 注意 Poppins 字体的加载
funk 把fonts.body和fonts.heading指向Poppins,但这只是 CSS 字体栈声明,并不会自动加载字体文件。Theme UI 官方文档站点的预设 Demo 组件(packages/docs/src/components/presets-demo.js)在渲染 funk 等预设时,通过<Helmet>显式加载了 Google Fonts 的Poppins:400,700,900,其中 900 正是 funk 中fontWeights.heading所需。你在自己的项目中同样需要自行引入 Poppins 字体资源(自托管或 CDN),并按需引入 400 / 700 / 900 三个字重,否则标题的 900 字重会退化为浏览器可用的最近字重。
五、把 funk 接入应用与 Gatsby
5.1 在 React 应用中使用
funk 与普通 Theme UI 主题用法一致,直接传给 Provider 即可:
import { ThemeProvider } from 'theme-ui' import funk from '@theme-ui/preset-funk' const App = () => ( <ThemeProvider theme={funk}> {/* 你的应用内容 */} </ThemeProvider> )ThemeProvider(新名称为ThemeUIProvider)的实现位于 packages/theme-provider/src/index.tsx:它组合了@theme-ui/core的 CoreProvider、@theme-ui/color-modes的ColorModeProvider,并在顶层注入RootStyles(将theme.styles.root应用到html,并默认启用box-sizing: border-box)。也就是说,funk 里继承自 base 的styles.root(fontFamily: 'body'、lineHeight: 'body'、fontWeight: 'body')会在接入 Provider 后自动成为全局正文样式。
5.2 在 Gatsby 中通过插件选项使用
对于 Gatsby 项目,官方提供了gatsby-plugin-theme-ui。根据 packages/gatsby-plugin-theme-ui/README.md,可以在gatsby-config.js的插件options.preset中直接填写包名字符串,插件会自动把它作为基础主题加载:
// gatsby-config.js module.exports = { plugins: [ { resolve: 'gatsby-plugin-theme-ui', options: { prismPreset: 'night-owl', preset: '@theme-ui/preset-funk', }, }, ], }插件的preset选项支持两种形式:JSON 主题对象或包名字符串(需先安装到项目依赖中),且假定主题对象以export default导出——funk 恰好满足(源码末尾export default funk)。插件文档还说明,选项中传入的主题模块被视为基础主题,之后任何 shadowing 自定义都会与它合并,这与"预设作为起点被扩展"的理念一致。
六、如何预览 funk 的效果
Theme UI 官方文档站点提供了一个预设预览页(packages/docs/src/pages/demo.mdx),其实现组件 packages/docs/src/components/presets-demo.js 从@theme-ui/presets读取全部预设,通过下拉框在ThemeUIProvider中切换,并借助@theme-ui/style-guide的ColorPalette、TypeScale、HeadingStyle、FontFamily等组件直观展示各预设的色板与排版效果。在本地以开发模式启动packages/docs站点后访问 Demo 页面,即可实时对比 funk 与其他预设,并查看其展开后的原始 JSON 主题对象。
七、总结与延伸阅读
@theme-ui/preset-funk是一个体积小、结构清晰的预设实现:它复用preset-base的全部量表与内容样式,仅通过覆盖fonts、lineHeights、fontWeights、colors四类字段形成鲜明风格,并用makeTheme保证类型精确性。把它当作扩展起点,可以快速搭建风格一致又具备个人辨识度的主题。
想继续深入,可以在当前仓库中查看以下文件:
- packages/preset-funk/src/index.ts:funk 预设全部源码;
- packages/preset-base/src/index.ts:funk 的基础量表与
styles来源; - packages/css/src/utils.ts:
makeTheme类型约束工具; - packages/presets/src/index.ts 与 packages/presets/test/index.tsx:聚合导出与合法性测试;
- packages/gatsby-plugin-theme-ui/README.md:Gatsby 中通过
preset选项接入 funk 的完整配置说明; - packages/docs/src/components/presets-demo.js:官方演示组件的实现,可参考其 Poppins 字体加载与切换逻辑。
如果你希望为 Theme UI 贡献新的预设或改进现有预设,仓库的预设包集中在packages/preset-*目录下,各包均遵循"基础量表 + 局部覆盖 +makeTheme约束"的同一套模式。
- 前端
- UI组件
- 设计系统
【免费下载链接】theme-ui
Build consistent, themeable React apps based on constraint-based design principles
相关推荐
深入解析 theme-ui 的 Polaris 预设:基于 Shopify 设计系统的主题扩展实践
深入解析 theme ui 的 Polaris 预设:基于 Shopify 设计系统的主题扩展实践 导读 @theme ui/preset polaris 是
前端UI组件设计系统@theme-ui/preset-bulma 预设深度解析:从安装、设计令牌到主题定制
@theme ui/preset bulma 预设深度解析:从安装、设计令牌到主题定制 本篇以 packages/preset bulma/README.md
前端UI组件设计系统FunClip:克隆即可启动的本地 AI 视频剪辑工具,按说话人裁剪并自动生成字幕
FunClip:克隆即可启动的本地 AI 视频剪辑工具,按说话人裁剪并自动生成字幕 FunClip 是一款本地部署的开源 AI 视频剪辑工具,调用 FunASR
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考