mcpField
P
panda-css-theming

panda-css-theming

github.com/chakra-ui设计与 UI6.2k71 次安装今天更新
代码仓库

关于 panda-css-theming

这是面向 Panda CSS 主题与设计系统架构的 skill,帮助团队规划 token 层级、主题、复合样式及 recipe 的职责与命名。它通过检查现有配置和调用点,指导何时将局部样式提升为共享设计系统 API。

主要功能

  1. 01规划原始与语义 token 层级
  2. 02设计主题和明暗模式
  3. 03区分复合样式、recipe 与 slot recipe
  4. 04制定 token 和变体命名规则
  5. 05检查已解析的设计系统规格
  6. 06判断样式何时提升为 recipe

适用场景

  1. 01为 Panda CSS 项目建立 token 体系
  2. 02统一主题与明暗模式处理
  3. 03将重复组件样式整理为 recipe
  4. 04审查现有设计系统的 token 来源

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/chakra-ui/panda --skill panda-css-theming

安装后在 Claude Code 中输入/panda-css-theming确认已加载。

详细介绍

Panda CSS 主题设计与系统设计

这是架构层:一个令牌叫什么、属于哪一层,以及样式何时应从调用点升级为配方。这些选择一旦代码库中有了数百个调用点,就很难更改。

两个问题就能解答大部分疑问:

  • 这个值有含义,还是只是一个值? 有含义的值放入 semanticTokens。
  • 这个文件之外是否有人需要改变它? 如果需要,它就是配方;如果不需要,就用 css()。

术语

请准确使用这些词。Panda 的配置键以它们命名,因此这里用词不严谨,之后就会变成错误的配置键。每个条目都说明了应避免的词以及原因。

原始令牌:theme.tokens 下的原始刻度值,按其内容命名(blue.500、spacing.4)。避免使用:“变量”(Panda 会根据令牌生成 CSS 变量,两者并不相同),“基础令牌”(看起来像 { base: … },而它是一个条件)。

语义令牌:theme.semanticTokens 下的值,按其作用命名(fg.muted、danger),并且可以随条件变化。避免使用:“别名”(别名意味着一个固定目标,而语义令牌可以在不同条件下取不同的值),“主题变量”。

复合样式:textStyles、layerStyles 或 animationStyles 下的一组具名属性。避免使用:“预设”(在 Panda 中,预设是可共享的配置包,完全是另一回事),“混入”。

配方:组件的样式及其变体,可以来自文件中的 cva,也可以来自配置中的 theme.recipes。避免使用:“变体函数”,“样式对象”(样式对象是传给 css() 的普通参数)。

插槽:插槽配方中一个具名且独立设置样式的部分。避免使用:“部件”、“元素”、“子组件”。配置键是 slots,使用其他词就会偏离主题。

解剖结构:组件公开的所有插槽的完整列表。这是公共 API。避免使用:“结构”、“形状”。

变体轴:variants 下的一个键(size、visual),其中包含该轴上的选项。避免使用:“属性”(属性是使用者传入的,轴是你定义的),“修饰符”。

复合变体:当特定的轴组合匹配时触发的规则。避免使用:“组合变体”,“多重变体”。

模式:可复用的布局函数(stack、grid、container)。_避免_将模式称为组件,也避免将配方称为模式。Panda 使用这两个词分别表示不同概念,也对应不同的配置键。

条件:状态或媒体选择器键(_hover、_dark、md)。避免使用:“修饰符”,“伪类”(条件也涵盖媒体查询和选择器)。

第 1 步:了解现有内容

  1. panda.config.ts:presets、theme、semanticTokens、conditions、strictTokens、designSystem。
  2. <outdir>/types/tokens.d.ts:当前所有可解析的令牌名称。
  3. <outdir>/specs/design-system.json:包含相同令牌及其值、条件、配方和各自来源预设的信息。参见设计系统规范。
  4. <outdir>/recipes/:现有配方。
  5. 搜索你准备令牌化的属性在若干调用点中的用法。如果已有 8 个文件都将 gray.600 用作次要文本颜色,那缺少的就是这个语义令牌。

不要脱离现有内容凭空设计。预设(@pandacss/preset-base、@pandacss/preset-panda)已经提供了一些刻度。应在其基础上扩展,而不是替换它们。

Token 层

四个层,每个层各有不同的职责。将值放在仍能准确描述它的最高层。

1. 基础 token 位于 theme.tokens。不带用途判断的原始刻度值。

tokens: {
  colors: { blue: { 500: { value: '#3b82f6' } } },
  spacing: { 4: { value: '1rem' } },
}

规则:提供完整刻度,而不只是今天需要的三个色阶。根据它_是什么_来命名(blue.500),绝不根据它的用途命名。 基础 token 的名称中不应提及任何组件。

2. 语义 token 位于 theme.semanticTokens。描述值_代表什么_,以及它如何随条件变化。

semanticTokens: {
  colors: {
    fg: { value: { base: '{colors.gray.900}', _dark: '{colors.gray.50}' } },
    'fg.muted': { value: { base: '{colors.gray.600}', _dark: '{colors.gray.400}' } },
    danger: { value: '{colors.red.600}' },
  },
}

规则:

  • 处理浅色/深色模式的唯一合理位置就在这里。将两个值配对放在同一个 token 中;不要在调用处添加 _dark 分支, 而应在这里一次性处理。
  • 按角色命名:fg、fg.muted、bg.subtle、border、danger。不要用 gray600Text,也不要用 buttonBlue。
  • 仅由一个组件使用的 token 是配方值,而不是语义 token。等出现第二个使用者时再提升它,不要提前。
  • 使用 {colors.gray.900} 引用基础 token。不要将十六进制值直接写入语义 token。

3. 复合样式是 textStyles、layerStyles 和 animationStyles。将经常一起使用的一组属性命名并打包。

textStyles: {
  heading: {
    h1: { value: { fontSize: '2.5rem', fontWeight: 'bold', lineHeight: '1.2' } },
  },
}

当相同的三到五个属性总是一起出现,并且团队已经会用一个名称来称呼它们时,就使用复合样式。 如果它需要变体,那它就是配方。

4. 配方是带有变体的组件样式。见下文。

命名
  • 优先按角色而不是外观命名。danger 在品牌重塑后仍然适用;red 则不然。
  • 使用点号表示层级:fg、fg.muted、fg.subtle。Panda 会将它们扁平化到同一个命名空间,因此前缀就是分组。
  • 选择一个维度并始终遵循。在同一个主题中混用 bg.subtle 和 subtleBackground 的代价比采用其中任一种都高。
  • 留意关键字冲突。在 lineHeights 中名为 normal 的 token 会覆盖 CSS 关键字。请重命名该 token。

主题和颜色模式

语义 token 包含两种值。仍然需要有机制来决定应用哪一个。

颜色模式

使用基础预设时,_dark 表示“位于带有 dark 类的元素内”。切换该类后,所有语义 token 都会切换,组件无需知道发生了变化。

<html class="dark"></html>

若要同时匹配数据属性,请重新定义条件。这属于配置更改,因此完成后要运行 codegen。

conditions: {
  extend: {
    dark: '.dark &, [data-theme="dark"] &',
  },
}

不要使用类似 useColorModeValue 的钩子,也不要在组件中添加分支。条件本身就是实现机制。

多个主题

themes 通过一个属性承载多个备选 token 值。只覆盖与默认值不同的部分,并确保每个被覆盖的值都包含两种模式。

themes: {
  gothic: {
    semanticTokens: {
      colors: {
        canvas: { value: { base: '{colors.gray.100}', _dark: '{colors.black}' } },
        accent: { value: { base: '{colors.purple.700}', _dark: '{colors.purple.300}' } },
      },
      radii: { control: { value: '{radii.none}' } },
    },
  },
}
<html data-panda-theme="gothic"></html>

主题和模式是两个独立维度。data-panda-theme 用于选择主题,dark 类用于选择主题内的模式,两者可以叠加。主题会应用于带有该属性的元素及其所有子元素,因此可以在 <html> 上设置一个主题,并在其中的面板上设置另一个主题。

发布主题

除非指定发布方式,否则 Panda 不会为 themes 生成任何内容。有两个选项,彼此独立。

在主样式表中。 在 staticCss.themes 中列出主题。每个主题都会在 tokens 层中生成一个变量块,其中只包含它覆盖的内容。无需运行时开销,只需一次请求,但每个页面都要加载所有主题。

staticCss: {
  themes: ['matcha', 'gothic'],
}

按需加载。 <outdir>/themes 会为每个主题提供单独的 JSON 文件,按需获取。主题很多,或用户会在运行时选择主题时,选择此方式。

import { getTheme, injectTheme } from '../styled-system/themes'

const theme = await getTheme('gothic')
injectTheme(document.documentElement, theme)

injectTheme 会在元素上设置 data-panda-theme,并追加一个包含该主题变量的 <style>。

如何选择
  • 只有一种外观,包含浅色和深色模式:使用带 _dark 的语义 token。不需要 themes 块。
  • 少量固定品牌:使用 themes 和 staticCss.themes。
  • 主题很多,或由用户在运行时选择:使用 themes 和 getTheme / injectTheme。

如果一个主题覆盖了大部分 token 集合,通常说明语义层不够完善。主题应该覆盖角色,而不是重建刻度。

设计系统规范

此规范为可选项。将 --spec 传给 panda codegen、panda build 或 panda lib,它会写入 <outdir>/specs/design-system.json。--spec=<file> 可将文件写到其他位置。不带此标志时不会写入任何文件,而且 panda dev 不接受此标志。

它将完整解析后的系统放在一个文件中,无需服务端,也不涉及 MCP:

  • schemaVersion,以及将每个令牌类别映射到 paths 对应部分的 categories
  • paths 和 tokens,提供每个令牌的类别和 cssVar
  • values,将每个令牌与其解析后的值配对(animations.spin 的值为 spin 1s linear infinite)
  • conditions,提供每个名称对应的实际选择器(_hover 的值为 &:is(:hover, [data-hover]))
  • sources,指出每个令牌来自哪个预设或文件,tokens[path].source 会索引到此处
  • themes,以及项目定义时才会包含的 recipes、slotRecipes、patterns、textStyles、layerStyles、 animationStyles、keyframes 和 colorPalettes。空部分会被省略。

当你需要查询令牌的值、其 CSS 变量或定义它的预设时,请读取此文件。如果只想快速确认某个名称是否存在, <outdir>/types/tokens.d.ts 中的联合类型更轻便,也更容易用 grep 搜索。

如果找不到该文件,通常是因为没人传入 --spec,而不是 codegen 失败了。运行 panda codegen --spec。不要改为读取 panda.config.ts:它记录的是有人编写的内容,而不是 Panda 合并预设后解析出的结果;启用 @pandacss/preset-base 和 @pandacss/preset-panda 后,大部分令牌集 都不会出现在其中。

如果文件存在但已过时,那是因为它由先前的运行生成。它不会自动刷新,因此请再次运行 panda codegen --spec。

审查主题时,从这里开始。source 为预设的令牌属于继承内容,因此项目自己的新增项是那些 source 为本地来源的令牌,而差异通常就出现在这里。

根据它编写 DESIGN.md

如果团队想为 Agent 提供一份可读的说明,请基于此文件生成,而不要手动编写。规范提供了所有 事实:令牌名称、值、条件、配方变体和来源信息。它无法提供的是意图,也就是何时该用 fg.muted 而不是 fg、哪些比例不可使用,以及品牌有哪些禁忌。

编写意图,生成其余内容,并在文件中注明它来自哪次规范生成。不要在说明文字中手动复制令牌值。 下次有人编辑主题时,这些值就会过时;而重新生成只需运行一次 panda codegen --spec。

如果项目希望定期生成此文件,应将 --spec 加入构建脚本,而不要依赖某人记得传入此标志。

何时将样式升级为配方

逐级升级。每次只升一级,并且仅在测试通过时升级。

调用处的 css({ … })。 一切从这里开始。样式只用一次时,就保持这样。

↓ 同一个样式对象出现在三个或更多位置

组件文件中的 cva。 一个包含变体的本地配方,与唯一的使用者放在一起。

import { cva } from '../styled-system/css'

const button = cva({
  base: { px: '4', py: '2', rounded: 'md' },
  variants: {
    visual: { solid: { bg: 'accent' }, outline: { borderWidth: '1px' } },
    size: { sm: { fontSize: 'sm' }, md: { fontSize: 'md' } },
  },
  defaultVariants: { visual: 'solid', size: 'md' },
})

↓ 此文件之外的使用者需要这些变体,或者这些样式属于设计系统而不是组件

配置配方。 在 panda.config.ts 的 theme.recipes 下使用 defineRecipe。此时变体 API 就成为系统的一部分, 类型会生成,CSS 也会从配置中输出。

↓ 组件渲染了多个元素,且每个元素都需要自己的样式

插槽配方。 在 theme.slotRecipes 下使用 defineSlotRecipe,或在文件中使用 sva。

不需要升级时就别升级。只在一个文件中使用的样式却定义为配置配方,会增加间接层和类型开销。

适合使用槽位配方的情况

如果组件会渲染可供使用者独立设置样式或替换的部分,请使用槽位:root、trigger、content、title、 indicator。判断信号:

  • 你正在将 className 传递给内部元素以设置样式。
  • 你编写了嵌套选择器,深入访问子组件的标记。
  • 两个元素需要以不同方式响应同一个变体。
const card = sva({
  slots: ['root', 'header', 'body'],
  base: {
    root: { rounded: 'lg', borderWidth: '1px' },
    header: { px: '4', py: '3', borderBottomWidth: '1px' },
    body: { px: '4', py: '4' },
  },
  variants: {
    density: {
      compact: { header: { py: '2' }, body: { py: '2' } },
    },
  },
})

先设计组件结构。编写样式前先命名槽位。槽位列表是公开 API,因此之后重命名会破坏所有使用者。

将配方转换为槽位配方: 列出组件渲染的元素,为每个元素命名,将每个样式块移到对应槽位下,然后移动变体,让每个变体指定它作用的槽位。base 和每个变体都要从扁平样式对象改为以槽位为键的映射。然后将使用者从 createRecipeContext 切换为 createSlotRecipeContext。

在 JSX 中使用配方(v2)
  • createRecipeContext(recipe) 会为 cva 配方返回 { withContext }。
  • createSlotRecipeContext(recipe) 会为 sva 返回 { withRootProvider, withProvider, withContext }。 withRootProvider 用于自身不渲染槽位的 root。

两者都来自 <outdir>/jsx。v1 中的单一 createStyleContext 已移除。

设计变体 API

  • 按意图而非实现命名变体。 使用 visual、size、tone,不要用 isBlue。
  • 始终设置 defaultVariants。 否则,不传任何值的使用者只能得到基础样式。作为运行时 prop 传入的变体也只会生成基础样式和默认值。
  • 布尔变体使用 true / false 键。仅在确实只有两种情况时使用。
  • 复合变体会增加 CSS。 每种组合都会生成 CSS。4 种视觉样式、4 种尺寸和 3 种色调组成的矩阵,最多会生成 48 个代码块。在 v2 中,复合变体会在构建时立即生成。设置 optimize.smartCompoundVariants: true,可将输出范围缩小到实际使用的组合。
  • 优先增加变体,而不是复合变体。 如果矩阵中的大多数单元格都需要复合规则,说明维度设计不合理。
  • 支持响应式变体值(size={{ base: 'sm', md: 'md' }}),但仅适用于提取出的字面量。

对于启用了 isolatedDeclarations 的导出组件,请使用变体键进行注解,以免 CSS 进入 .d.ts:

import type { RecipeRuntimeFn } from '../styled-system/types'

export const button: RecipeRuntimeFn<{ visual?: 'solid' | 'outline' }> = cva({ … })

SlotRecipeRuntimeFn 和 StyledComponent<Tag, Props> 对 sva 和 styled 也有同样的作用。

反模式

  • 主题只是堆在一起的基础值。 如果调用处都引用 gray.600,品牌重塑时就得在整个应用中查找并替换。这正是语义层要解决的问题。
  • 在全局令牌中使用组件名称。 colors.buttonPrimary 意味着未来每个组件都会发明自己的名称。应按角色命名,而非按使用者命名。
  • 为每个组件重新定义尺度。 在 base 中重新声明间距值的配方,正逐渐偏离主题。
  • 在配方中编写页面布局。 配方用于组件。页面组合应使用 css() 和 patterns。
  • 在调用处区分深色模式。 在语义令牌中设置一个 _dark,胜过在组件中写五十个。
  • 防御性地添加槽位。 不设置样式的槽位也是你必须维护的 API。

更改配置后

运行 panda codegen(或 panda build)。在此之前,令牌、配方及其类型都不会生成。然后检查 CSS 差异。令牌重命名后若某个值悄然丢失,会表现为相应 CSS 不再生成。

满足以下条件即表示正常运行

  • 品牌重塑只需调整配置,不必修改调用点。
  • _dark 出现在 theme.semanticTokens 中,除此之外几乎不会出现。
  • 切换 dark 类或 data-panda-theme 会改变页面,且没有任何组件读取其中任一项。
  • themes 中的每个主题都已列在 staticCss.themes 中,或通过 getTheme 加载,因此不会有主题不生成任何内容。
  • 在组件中搜索 gray.600 这样的原始值,几乎找不到结果。
  • recipe 的 slots 数组中的每个插槽都有对应的样式。
  • 没有任何令牌名称包含组件名称。
  • panda codegen 运行无误,且生成的 recipe CSS 与你计划发布的变体一致。

另请参阅

  • 根据你设计的系统编写样式:调用 Skill 工具并输入 “panda-css”。
  • v1 到 v2 迁移,包括 createStyleContext 拆分:调用 Skill 工具并输入 “panda-css-migrate”。

评论

支持 Markdown