ui-ux-pro-max-skill ui-styling 实战指南:shadcn/ui 主题定制、CSS 变量与暗色模式实现
【免费下载链接】ui-ux-pro-max-skillAn AI skill that provides design intelligence for building professional UI/UX across multiple platforms.项目地址: https://gitcode.com/gh_mirrors/ui/ui-ux-pro-max-skill
本文基于 ui-ux-pro-max-skill 仓库中 ui-styling 技能的主题参考文档 shadcn-theming.md,系统讲解 shadcn/ui 的主题体系:暗色模式接入、CSS 变量令牌系统、Tailwind 映射配置、颜色/圆角定制与组件变体扩展。读完后,你可以直接在一套 React 应用中落地完整的 shadcn/ui 主题方案,并理解仓库内配套脚本与示例代码是如何验证这些流程的。
技能定位:主题文档在 ui-styling 中的角色
ui-ux-pro-max-skill 是一个为 AI Agent 提供 UI/UX 设计智能的开源技能仓库。其中 ui-styling 技能 负责 shadcn/ui 组件、Tailwind CSS 工具类样式与 Canvas 视觉设计,references/shadcn-theming.md是它的主题与定制参考,覆盖:
- next-themes 暗色模式接入;
- CSS 变量系统;
- 颜色定制与调色板;
- 组件变体定制;
- Theme Toggle 实现。
技能的主文档 SKILL.md 给出的前置流程是:
npx shadcn@latest init npx shadcn@latest add button card dialog forminit会提示选择框架、TypeScript、路径与主题偏好,并同时配置 shadcn/ui 和 Tailwind CSS。主题定制正是在init产出的components.json与globals.css之上进行的。另外,该文档在 CLI 资产树中存在镜像副本 cli/assets/skills/ui-styling/references/shadcn-theming.md,供 CLI 分发时引用。
暗色模式接入
shadcn/ui 的暗色模式采用class 策略:通过在<html>上切换darkclass,让 CSS 变量在:root与.dark两套取值之间切换。这要求 Tailwind 配置中启用darkMode: ["class"](见下文"Tailwind 配置"一节)。
Next.js App Router
第 1 步:安装 next-themes
npm install next-themes第 2 步:创建主题 Provider 包装组件
// components/theme-provider.tsx "use client" import * as React from "react" import { ThemeProvider as NextThemesProvider } from "next-themes" export function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) { return <NextThemesProvider {...props}>{children}</NextThemesProvider> }第 3 步:在根布局中包裹应用
// app/layout.tsx import { ThemeProvider } from "@/components/theme-provider" export default function RootLayout({ children }) { return ( <html lang="en" suppressHydrationWarning> <body> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange > {children} </ThemeProvider> </body> </html> ) }关键参数说明:
attribute="class":将主题写为<html>上的 class,与 Tailwind 的dark:变体联动;defaultTheme="system"+enableSystem:无本地偏好时跟随系统prefers-color-scheme;disableTransitionOnChange:切换主题时禁用过渡动画,避免颜色渐变闪烁;<html>上的suppressHydrationWarning用于抑制 SSR 与客户端 hydration 之间的属性差异告警。
第 4 步:主题切换组件
import { Moon, Sun } from "lucide-react" import { useTheme } from "next-themes" import { Button } from "@/components/ui/button" export function ThemeToggle() { const { setTheme, theme } = useTheme() return ( <Button variant="ghost" size="icon" onClick={() => setTheme(theme === "light" ? "dark" : "light")} > <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> <span className="sr-only">Toggle theme</span> </Button> ) }双图标通过rotate/scale过渡实现旋转淡入淡出,sr-only保证屏幕阅读器仍可感知"切换主题"。
仓库内的实际实现参考
仓库的 gallery 示例站 完整演示了这套方案。其 Provider 写法为:
"use client"; import { ThemeProvider } from "next-themes"; export function Providers({ children }: { children: React.ReactNode }) { return ( <ThemeProvider attribute="class" defaultTheme="system" enableSystem> {children} </ThemeProvider> ); }gallery/package.json 中固定了"next-themes": "^0.4.4",可以作为选型参考。
切换按钮 gallery/components/DarkModeToggle.tsx 在文档版本基础上多做了一处关键处理——mounted 守卫,用于规避 next-themes 的服务端渲染陷阱(SSR 阶段theme恒为"system",直接渲染会与服务端输出不一致):
const { theme, setTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => setMounted(true), []); if (!mounted) { return <div className="w-9 h-9" />; // 占位,防止布局跳动 } const isDark = theme === "dark";挂载前渲染固定尺寸的占位元素,挂载后再渲染真实按钮,并附带aria-label说明切换目标。这个模式值得在任何使用useTheme的组件中沿用。
Vite 或其他框架
非 Next.js 项目可以用 next-themes,也可以直接用原生方案:切换darkclass 并用localStorage持久化偏好,首次访问时回退到系统偏好:
// Store preference function toggleDarkMode() { const isDark = document.documentElement.classList.toggle('dark') localStorage.setItem('theme', isDark ? 'dark' : 'light') } // Initialize on load if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark') }该方案的核心逻辑:已存dark→ 启用;无存储记录且系统偏好深色 → 启用;否则保持浅色。初始化脚本应内联在<head>中执行,以避免暗色模式下的白屏闪烁(FOUC)。
CSS 变量系统
shadcn/ui 的主题令牌以 CSS 自定义属性形式定义在globals.css中,@layer base内同时声明浅色(:root)与深色(.dark)两套取值:
@layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 212.7 26.8% 83.9%; } }这些变量遵循语义命名:background/foreground表达页面基底,primary/secondary/muted/accent表达角色而非具体色相,destructive表达"危险操作"含义,border/input/ring覆盖边框、输入框与焦点环。每个主色都配对一个-foreground变量,用于保证文字在该背景上的对比度。
颜色格式:裸 HSL 值
变量值采用不带hsl()包裹的 HSL 三分量格式,这是 shadcn/ui 的一个标志性设计:
--primary: 222.2 47.4% 11.2%; /* H S L */使用时手动拼入hsl(),好处是可以直接用现代 CSS 的斜杠语法控制不透明度,而无需再引入--primary-alpha之类的第二组变量:
background: hsl(var(--primary)); background: hsl(var(--primary) / 0.5); /* 50% opacity */如果写成--primary: hsl(222.2, 47.4%, 11.2%),则无法用hsl(var(--primary) / 0.5)这种形式调节透明度,这也是 shadcn/ui 组件库(如bg-primary/10这类 Tailwind 写法)能够成立的前提。
Tailwind 配置:把 CSS 变量映射为工具类
tailwind.config.ts负责把 CSS 变量桥接为 Tailwind 颜色与圆角工具类:
// tailwind.config.ts export default { darkMode: ["class"], theme: { extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, }, }, }要点:
darkMode: ["class"]启用 class 策略,是整套暗色模式的前提;- 每个令牌色都映射为
hsl(var(--*))形式,使bg-primary、text-muted-foreground、border-input等工具类自动获得运行时主题能力; - 圆角三档由单一
--radius派生:lg取原值,md减 2px,sm减 4px,形成层级一致的圆角体系。
仓库的 gallery/tailwind.config.ts 是最小化示例,其中darkMode: "class"与上表策略一致;gallery/app/globals.css 则演示了应用层如何配合暗色模式——在@layer base里给body设置@apply bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-100,让全局基底色随darkclass 切换。
自动化辅助:tailwind_config_gen.py
ui-styling 技能附带脚本 tailwind_config_gen.py,可以程序化生成tailwind.config.ts/.js。它的基线配置(_base_config)默认写入"darkMode": ["class"],并按框架预设content路径:
react:./src/**/*.{js,jsx,ts,tsx}与./index.html;nextjs:./app/**、./pages/**、./components/**三类 glob;vue/svelte亦有对应模板。
常用用法:
# 为 Next.js 生成 TypeScript 配置 python tailwind_config_gen.py --framework nextjs # 生成 JS 配置并注入自定义颜色(值可以是 hex 或 hsl(var(--primary)) 形式) python tailwind_config_gen.py --js --colors brand:#3b82f6 accent:#8b5cf6 # 追加自定义字体、间距、断点与推荐插件 python tailwind_config_gen.py --fonts display:"Playfair Display,serif" \ --spacing navbar:4rem --breakpoints 3xl:1920px --plugins--plugins会追加推荐项(默认tailwindcss-animate,Next.js 下额外推荐@tailwindcss/typography)并打印对应的npm install -D命令。从源码看,_format_plugins对插件名执行严格的 npm 包名正则校验(拒绝引号、括号、分号等字符)后才生成require('...')语句,以防配置注入(源码注释明确引用了 CWE-94)。配套测试见 test_tailwind_config_gen.py。
颜色定制:三种方法
方法 1:直接修改 CSS 变量
最直接的改色方式是编辑globals.css中的变量值,同时维护浅色与深色两套:
:root { --primary: 262.1 83.3% 57.8%; /* Purple */ --primary-foreground: 210 20% 98%; } .dark { --primary: 263.4 70% 50.4%; /* Darker purple */ --primary-foreground: 210 20% 98%; }注意深色下通常需要同步降低饱和度/明度,否则紫色在深色背景上会显得"发飘"。由于变量被 Tailwind 工具类全局引用,改一处即全站生效。
方法 2:主题生成器
可以使用 shadcn/ui 官方主题生成器(shadcn/ui 官方站点的 Themes 页面):选择基础色 → 生成主题 → 复制输出的一组 CSS 变量,整体替换globals.css中对应取值。这样生成的调色板在各语义角色间已做对比度配平,比手工逐变量试色更稳。
方法 3:多主题(data 属性)
在同一页面提供多套主题变体时,用data-theme属性承载额外变量组:
[data-theme="violet"] { --primary: 262.1 83.3% 57.8%; --primary-foreground: 210 20% 98%; } [data-theme="rose"] { --primary: 346.8 77.2% 49.8%; --primary-foreground: 355.7 100% 97.3%; }然后在任意容器上应用,变量作用域自动收敛到该子树:
<div>// components/ui/button.tsx const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium", { variants: { variant: { default: "bg-primary text-primary-foreground", destructive: "bg-destructive text-destructive-foreground", outline: "border border-input bg-background", // Add custom variant gradient: "bg-gradient-to-r from-purple-500 to-pink-500 text-white", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", // Add custom size xl: "h-14 rounded-md px-10 text-lg", }, }, defaultVariants: { variant: "default", size: "default", }, } )新增后可直接消费:
<Button variant="gradient" size="xl">Custom Button</Button>定制基础样式
也可以直接改写组件的根类名,例如给 Card 加更强的阴影:
// components/ui/card.tsx const Card = React.forwardRef< HTMLDivElement, React.HTMLAttributes<HTMLDivElement> >(({ className, ...props }, ref) => ( <div ref={ref} className={cn( "rounded-xl border bg-card text-card-foreground shadow-lg", // Modified className )} {...props} /> ))组件普遍把外部传入的className通过cn(tailwind-merge 封装)拼在默认类名之后,这为下一层覆盖留了口。
className 一次性覆盖
无需改源码的场景,直接传className即可:
<Card className="border-2 border-purple-500 shadow-2xl hover:scale-105 transition-transform"> Custom styled card </Card>决策顺序建议:一次性需求 →className;多处复现 → 提取自定义变体;全局默认 → 改组件源码或 CSS 变量。
辅助脚本:shadcn_add.py
组件文件本身由 shadcn_add.py 这类工具管理。该脚本封装 shadcn CLI,支持--all、--overwrite、--dry-run、--list与--project-root:
python shadcn_add.py button card dialog # 添加多个组件 python shadcn_add.py --all # 添加全部组件 python shadcn_add.py button --overwrite # 强制重装 python shadcn_add.py --list # 列出已安装组件从源码看,它有两处与主题流程直接相关的行为:一是先检查components.json是否存在,未init时直接报"shadcn not initialized. Run 'npx shadcn@latest init' first";二是_get_shadcn_version会从项目package.json的dependencies/devDependencies读取已锁定的 shadcn 版本来执行npx shadcn@<ver> add,读取不到时回退到内置的2.3.0,避免混装版本。已装组件的判定依据是components.json中aliases.components指向目录下实际存在的.tsx文件。回归测试见 test_shadcn_add.py。
基础色预设、风格变体与全局圆角
基础色预设(Base Color Presets)
init时 shadcn/ui 提供五组灰阶基底:
- Slate:偏冷的灰;
- Gray:中性灰;
- Zinc:偏暖的灰;
- Neutral:均衡灰;
- Stone:大地色灰。
初始化时选择,也可以事后通过更新 CSS 变量中的灰阶令牌(--background、--muted、--border等)更换。
组件风格变体(Style Variants)
两种组件风格:
- Default:更柔和、更圆角的观感;
- New York:更锐利、对比更强。
在init交互中选择,或在components.json中声明:
{ "style": "new-york", "tailwind": { "cssVariables": true } }tailwind.cssVariables: true即表示采用本文所述的 CSS 变量主题机制;切换 style 后重新add组件,会以对应风格的类名重写组件源码。
圆角全局控制
--radius一个变量控制全站圆角基调:
:root { --radius: 0.5rem; /* Default */ --radius: 0rem; /* Sharp corners */ --radius: 1rem; /* Rounded */ }配合 Tailwind 配置中的派生映射,rounded-lg/rounded-md/rounded-sm全部跟随该变量缩放,实现"改一处、全局一致"的圆角体系。
最佳实践
原文档给出的 7 条主题工程准则,结合仓库实现可以逐条落实:
- 使用 CSS 变量:令牌化是运行时切换主题(暗色、多主题)的基础,这也是 shadcn/ui
cssVariables模式的核心; - 前景色成对管理:每个语义色都配
-foreground,避免只改背景导致文字对比度崩坏; - 双主题验证:组件必须在 light 与 dark 两种模式下逐一检查,
dark:变体应覆盖所有主题化元素; - 语义化命名:用
destructive而非red、muted而非gray,让样式意图可读、可替换; - 可访问性:颜色对比度至少满足 WCAG AA(详见同目录参考文档 shadcn-accessibility.md);
- 组件覆盖优先 className:一次性差异用
classNameprop,不污染组件默认值; - 模式提取:反复出现的定制应沉淀为自定义 cva 变体或独立组件。
参考文件索引
| 用途 | 路径 |
|---|---|
| 主题参考文档(本文主体) | .claude/skills/ui-styling/references/shadcn-theming.md |
| ui-styling 技能主文档 | .claude/skills/ui-styling/SKILL.md |
| 组件安装脚本 | .claude/skills/ui-styling/scripts/shadcn_add.py |
| Tailwind 配置生成脚本 | .claude/skills/ui-styling/scripts/tailwind_config_gen.py |
| 安装脚本测试 | .claude/skills/ui-styling/scripts/tests/test_shadcn_add.py |
| 配置生成脚本测试 | .claude/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py |
| 暗色模式 Provider 示例 | gallery/app/providers.tsx |
| 主题切换按钮示例 | gallery/components/DarkModeToggle.tsx |
| 全局样式(暗色适配) | gallery/app/globals.css |
| Tailwind 配置示例 | gallery/tailwind.config.ts |
| 依赖版本(next-themes) | gallery/package.json |
【免费下载链接】ui-ux-pro-max-skillAn AI skill that provides design intelligence for building professional UI/UX across multiple platforms.项目地址: https://gitcode.com/gh_mirrors/ui/ui-ux-pro-max-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考