首页
/ Next.js 集成 Panda CSS:四种类型安全样式方案的配置原理与实战实现

Next.js 集成 Panda CSS:四种类型安全样式方案的配置原理与实战实现

2026-09-06 17:43:07作者:裘旻烁

本文基于 Next.js 官方仓库中的 panda-css 示例,讲解如何在一个 App Router 项目中接入 Panda CSS 这一零运行时 CSS 解决方案。示例项目用同一组"链接"演示了 Panda CSS 支持的四种样式编写方式——Atomic Recipe(CVA)、Atomic Style、Config Recipe 与 Text Styles,并完整给出了 Panda 配置文件、PostCSS 入口与全局 CSS 的搭建细节。读完本文,你能够理解 Panda 各配置项的用途、四种样式方案的差异与取舍,并掌握从零初始化一个 Panda CSS 项目的完整步骤。

一、示例项目总览:一个项目演示四种样式方案

Panda CSS 是一个零运行时、构建时生成原子 CSS 的方案:它通过扫描源码中的样式声明,在构建阶段提取并生成最小化的 CSS,从而避免运行时计算样式带来的开销。Next.js 仓库中的这个示例(examples/panda-css)刻意用最简单的场景——一个链接——分别以 Panda 支持的四种方式实现同样的视觉效果,方便读者对照理解各方案的定位:

  • Atomic Recipe(CVA):以类型安全的运行时 API 创建多变体原子样式。样式定义写在组件文件里,按需调用 cva 生成 className。
  • Atomic Style:可复用的原子样式,类型安全且 CSS 体积开销小。
  • Config Recipe:在 Panda 配置中定义的"配置式配方",被提取出来并在构建时即时(just-in-time)生成。
  • Text Styles:全局文本样式,用于统一、可读性一致的排版。

从源码结构看,项目页面 app/page.tsx 就是一个极简的展示页:一个 flex 列布局的 <main> 容器,依次渲染四个演示组件:

import { css } from "@/styled-system/css";
import LinkWithAtomicStyle from "@/app/components/link-with-atomic-style";
import LinkWithAtomicRecipe from "@/app/components/link-with-atomic-recipe";
import LinkWithConfigRecipe from "@/app/components/link-with-config-recipe";
import LinkWithTextStyles from "@/app/components/link-with-text-styles";

const styles = css({
  display: "flex",
  flexDirection: "column",
  alignItems: "center",
  gap: "2rem",
  p: "6rem",
  minH: "100vh",
});

export default function Home() {
  return (
    <main className={styles}>
      <LinkWithAtomicStyle />
      <LinkWithAtomicRecipe />
      <LinkWithConfigRecipe />
      <LinkWithTextStyles />
    </main>
  );
}

注意其中的两个细节:

  1. css 函数不是手写的,而是从 @/styled-system/css 导入——styled-system 目录由 Panda 在构建时自动生成(见下文 outdir 配置),这也解释了为什么 tsconfig.jsoninclude 中显式包含了 "styled-system",以便 TypeScript 能识别生成的类型声明;
  2. 每个组件内的链接都附带 <span className="icon">-&gt;</span> 箭头装饰,其样式来自 Panda 配置里的全局 CSS(见下文)。

依赖与版本前提

package.json 可以看到示例的依赖组合:

  • next: "latest"reactreact-dom^18.2.0
  • Panda 相关依赖只有 @pandacss/dev^0.15.1),作为 devDependency 安装——Panda 是纯构建时工具,运行时不需要它;
  • 脚本中有一条关键配置:"prepare": "panda codegen",即每次安装依赖时自动执行 panda codegen,生成 styled-system 类型与原子类名映射。
{
  "private": true,
  "scripts": {
    "prepare": "panda codegen",
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint ."
  }
}

二、核心配置解析:panda.config.ts

整个示例的"灵魂"是 panda.config.ts。下面按配置项逐一拆解。

2.1 文本样式定义(Text Styles)

import {
  defineConfig,
  defineGlobalStyles,
  defineRecipe,
  defineTextStyles,
} from "@pandacss/dev";

export const textStyles = defineTextStyles({
  link: {
    description: "The classic link text style - used in demo links",
    value: {
      fontSize: "lg",
      fontFamily: "inter",
    },
  },
});

defineTextStyles 定义了名为 link 的文本样式,包含字号(lg)与字体族(inter)。它会被挂到 theme.extend.textStyles,之后任何地方都可以直接用 textStyle: "link" 引用,实现排版规则的集中管理。

2.2 配置式配方(Config Recipe)

export const linkRecipe = defineRecipe({
  className: "link",
  description: "The styles for the link component",
  base: {
    color: {
      _default: "gray.800",
      _osDark: "gray.100",
    },
    fontFamily: "inter",
  },
  variants: {
    size: {
      sm: { fontSize: "sm" },
      lg: { fontSize: "lg" },
    },
  },
  defaultVariants: {
    size: "lg",
  },
});

这里体现了 Panda 的几个典型特性:

  • className: "link":声明生成类名前缀为 link,构建后组件端即可通过生成的 link() 函数(见 link-with-config-recipe.tsx)按需取用;
  • _default / _osDark 条件语法:这是 Panda 的"自响应式"(self-responding)语法——_osDark 表示"当操作系统处于深色模式时",无需手写 @media (prefers-color-scheme: dark)
  • variants + defaultVariants:声明式变体系统,sizesm/lg 两个字号,默认 lg

2.3 全局样式

const globalCss = defineGlobalStyles({
  html: {
    bg: {
      _default: "white",
      _osDark: "black",
    },
    "& .icon": {
      ml: 2,
      fontSize: "lg",
      fontWeight: 700,
    },
  },
});

两个值得注意的点:html 背景色同样使用了深色模式自响应;& .icon 选择器为页面中所有带 icon 类的箭头装饰统一设置左边距、字号与字重——这正是四个演示组件里 <span className="icon">-&gt;</span> 的样式来源。全局样式集中定义在配置中,而不是散落在各处 CSS 文件里。

2.4 主配置:defineConfig

export default defineConfig({
  // Whether to use css reset
  preflight: true,

  // Where to look for your css declarations
  include: ["./app/**/*.{js,jsx,ts,tsx}"],

  // Files to exclude
  exclude: [],

  // Useful for theme customization
  theme: {
    extend: {
      tokens: {
        fonts: {
          inter: { value: "var(--font-inter)" },
        },
      },
      recipes: {
        link: linkRecipe,
      },
      textStyles,
    },
  },

  // The output directory for your css system
  outdir: "styled-system",

  // Global styles
  globalCss,
});

各配置项的作用:

配置项 取值 作用
preflight true 启用 CSS reset,统一各浏览器默认样式
include ./app/**/*.{js,jsx,ts,tsx} 样式扫描范围,Panda 只解析匹配文件中的 csscva 等调用
exclude [] 排除文件列表
theme.extend.tokens.fonts.inter var(--font-inter) 扩展设计令牌,把 inter 字体族指向 CSS 变量 --font-inter
theme.extend.recipes.link linkRecipe 注册配置式配方,生成 styled-system/recipes 中的 link 导出
theme.extend.textStyles textStyles 注册 link 文本样式
outdir styled-system 类型与原子类映射的输出目录,组件正是从 @/styled-system/... 导入
globalCss 上文定义 注入全局样式

其中 tokens.fonts.inter: "var(--font-inter)" 与 Next.js 的字体方案衔接得很巧妙:Panda 并不自己下载 Inter 字体,而是引用 Next.js next/font 注入的 CSS 变量(见下节),让两套系统各取所长。

三、字体接入:next/font 与 Panda 令牌协作

app/layout.tsx 展示了 Panda 与 Next.js 字体系统的标准接法:

import "./global.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";

const InterFont = Inter({
  weight: ["400", "500", "700"],
  display: "swap",
  subsets: ["latin"],
  variable: "--font-inter",
});

export const metadata: Metadata = {
  title: "Create Next App with Panda CSS",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={`${InterFont.variable}`}>
      <body>{children}</body>
    </html>
  );
}

调用链非常清晰:Inter()variable: "--font-inter" 让 Next.js 在 <html> 上输出 --font-inter CSS 变量,<html className={InterFont.variable}> 负责挂上字体变量类;而 panda.config.ts 中的 tokens.fonts.inter 恰好声明了 var(--font-inter)。于是组件里写 fontFamily: "inter" 时,Panda 生成的 CSS 最终解析到 Next.js 自托管的 Inter 字体变量上——字体加载、字体令牌、原子类三层完全解耦又互相咬合。

同时注意 import "./global.css" 必须放在根 Layout 中,它是 Panda 输出的 CSS 入口。

四、入口 CSS 的 Layer 声明与 PostCSS 插件

Panda CSS 基于 CSS Cascade Layers 工作,入口 CSS 文件 app/global.css 的全部内容只有一行:

/* https://panda-css.com/docs/installation/nextjs#configure-the-entry-css-with-layers */
@layer reset, base, tokens, recipes, utilities;

这行声明确立了五层的优先级顺序:reset < base < tokens < recipes < utilities。Panda 构建时生成的 reset、令牌、配方与工具类会分别落入对应层,原子类(utilities 层)天然具有最高优先级,覆盖关系因此可预测。

构建链路的另一端是 postcss.config.js

module.exports = {
  plugins: {
    "@pandacss/dev/postcss": {},
  },
};

Next.js 在编译 global.css 时会经过 PostCSS,@pandacss/dev/postcss 插件负责扫描 include 范围内的源码、收集 css/cva 调用,并在构建时生成原子类 CSS 与 styled-system 类型输出。而 next.config.js 是空的(const nextConfig = {};)——接入 Panda 完全不需要改动 Next.js 配置,这也是"构建时方案、零运行时依赖"的直观体现。

五、四种样式方案的组件实现对照

5.1 Atomic Style:组件内直接声明

link-with-atomic-style.tsx 是最直接的用法:

import { css } from "@/styled-system/css";

const styles = css({
  fontSize: "lg",
  color: {
    _default: "gray.800",
    _osDark: "gray.100",
  },
  fontFamily: "inter",
});

export default function LinkWithAtomicStyle() {
  return (
    <a
      className={styles}
      href="https://panda-css.com/docs/concepts/writing-styles#atomic-styles"
      target="_blank"
      rel="noreferrer"
    >
      Link with <b>atomic style</b>
      <span className="icon">-&gt;</span>
    </a>
  );
}

css() 返回一个类名字符串,Panda 从参数中提取出 font-sizecolorfont-family(含深色模式变体)等原子类。适合一次性、单组件使用的样式。

5.2 Atomic Recipe(CVA):组件内的变体系统

link-with-atomic-recipe.tsx 把变体逻辑提升到组件内:

import { cva } from "@/styled-system/css";

const styles = cva({
  base: {
    color: {
      _default: "gray.800",
      _osDark: "gray.100",
    },
    fontFamily: "inter",
  },
  variants: {
    size: {
      sm: { fontSize: "sm" },
      lg: { fontSize: "lg" },
    },
  },
  defaultVariants: {
    size: "lg",
  },
});

export default function LinkWithAtomicRecipe() {
  return (
    <a
      className={styles({ size: "lg" })}
      href="https://panda-css.com/docs/concepts/recipes#atomic-recipe-or-cva"
      target="_blank"
      rel="noreferrer"
    >
      Link with <b>atomic recipe</b>
      <span className="icon">-&gt;</span>
    </a>
  );
}

cva(class variants authority)是"原子化配方":base 是共享基底,variants 声明可切换的属性组合,defaultVariants 给出默认取值,调用 styles({ size: "lg" }) 时按变体组合出最终类名。与 Config Recipe 相比,它的定义写在组件文件里、贴近使用点,适合"只有当前组件关心这套变体"的场景。

5.3 Config Recipe:集中定义、即时生成

link-with-config-recipe.tsx 只有一段核心代码:

import { link } from "@/styled-system/recipes";

export default function LinkWithConfigRecipe() {
  return (
    <a
      className={link({ size: "lg" })}
      href="https://panda-css.com/docs/concepts/recipes#config-recipe"
      target="_blank"
      rel="noreferrer"
    >
      Link with <b>config recipe</b>
      <span className="icon">-&gt;</span>
    </a>
  );
}

样式本体在 panda.config.ts 中以 defineRecipe 声明并经 theme.extend.recipes.link 注册;Panda 据此在构建时"即时"生成 styled-system/recipes 模块导出的 link 函数。多组件复用同一套变体语义时,这是更收敛的做法——配方只有一份事实来源。

5.4 Text Styles:排版归一化

link-with-text-styles.tsx 展示了文本样式与原子样式的组合:

import { css } from "@/styled-system/css";

const styles = css({
  textStyle: "link",
  color: {
    _default: "gray.800",
    _osDark: "gray.100",
  },
});

export default function LinkWithTextStyles() {
  return (
    <a
      className={styles}
      href="https://panda-css.com/docs/theming/text-styles#defining-text-styles"
      target="_blank"
      rel="noreferrer"
    >
      Link with <b>text styles</b>
      <span className="icon">-&gt;</span>
    </a>
  );
}

textStyle: "link" 一行即引入配置中定义的 fontSize: "lg"fontFamily: "inter",颜色等其余属性仍由局部 css 声明。四个方案对同一个"链接"实现了完全一致的视觉结果,但样式的事实来源与复用粒度各不相同:Text Styles 管排版、Atomic Style 管一次性原子声明、Atomic Recipe 管组件局部变体、Config Recipe 管跨组件共享配方。

六、本地运行与项目初始化

官方 README 提供了通过 create-next-app 一键引导示例的完整命令(支持 npm、Yarn、pnpm、Bun 四种包管理器):

npx create-next-app --example panda-css panda-css-app
yarn create next-app --example panda-css panda-css-app
pnpm create next-app --example panda-css panda-css-app
bunx create-next-app --example panda-css panda-css-app

执行后进入 panda-css-app 目录,pnpm install(安装时 prepare 脚本会自动执行 panda codegen),然后:

pnpm dev     # 开发模式
pnpm build   # 生产构建
pnpm start   # 启动生产服务

若从本仓库直接使用示例目录,配置要求与上面一致:安装依赖后 panda codegen 会生成 styled-system 输出目录,next dev 的 PostCSS 编译会经 @pandacss/dev/postcss 插件产出原子 CSS。README 还提及可将项目部署到 Vercel 云端(deployment 文档见 Next.js 官方文档),部署时无需额外服务,产物即标准 Next.js 构建输出。

接入要点回顾

  1. 安装 @pandacss/dev(devDependency),PostCSS 中启用 @pandacss/dev/postcss
  2. 编写 panda.config.ts:设置 include 扫描范围、outdir 输出目录、preflight、主题令牌与配方;
  3. 在根 Layout 引入入口 CSS,并用 @layer reset, base, tokens, recipes, utilities; 声明层序;
  4. next/fontvariable 注入 CSS 变量,再通过 Panda 的 tokens.fonts 引用,打通字体链路;
  5. 按"复用范围"选择样式方案:一次性用 css,组件变体用 cva,跨组件共享用 Config Recipe,排版统一用 Text Styles。

按这条链路,你获得的是一套构建时零运行时开销、类型完备、且与 Next.js App Router 字体/布局体系无缝协作的样式基建。

登录后查看全文
热门项目推荐
相关项目推荐