关于 styling-system
styling-system 是一项面向前端项目的样式设计与实现技能,帮助开发者选择 CSS 架构并搭建一致的样式系统。它涵盖设计令牌、主题、响应式布局及组件变体,并提供验证脚本和比较参考。
主要功能
- 01比较 Tailwind、CSS Modules、CSS-in-JS 与原生 CSS
- 02规划样式配置与架构
- 03建立并集成设计令牌
- 04设计暗色模式和响应式布局
- 05构建组件变体与状态样式
- 06使用脚本验证样式实现
适用场景
- 01为新项目选择样式方案
- 02为现有界面添加暗色模式
- 03统一组件的设计令牌与变体
- 04迁移样式架构并检查实现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgd34msu/ goodvibes-plugin --skill styling-system安装后在 Claude Code 中输入/styling-system确认已加载。
附带文件references/styling-comparison.mdscripts/validate-styling.sh
详细介绍
资源
scripts/
validate-styling.sh
references/
styling-comparison.md
样式系统
本技能将指导你使用 GoodVibes 精准工具做出 CSS 架构决策并进行实现。在搭建样式基础设施、创建设计系统或实现主题与响应式模式时,请使用此工作流。
何时使用此技能
在以下情况下加载此技能:
- 为新项目搭建样式基础设施
- 在 Tailwind、CSS Modules、CSS-in-JS 或原生 CSS 之间进行选择
- 实现设计令牌系统
- 添加深色模式支持
- 创建响应式布局和断点策略
- 在不同样式方案之间迁移
- 构建组件变体系统
- 搭建动画模式
触发短语:“搭建样式”“添加 Tailwind”“实现深色模式”“设计令牌”“响应式设计”“CSS 架构”“主题系统”。
核心工作流
阶段 1:发现
在做出样式决策之前,先了解项目的当前状态。
步骤 1.1:检测现有样式方案
使用 discover 查找整个代码库中的样式模式。
discover:
queries:
- id: tailwind_usage
type: grep
pattern: "(className=|class=).*\".*\\b(flex|grid|text-|bg-|p-|m-)"
glob: "**/*.{tsx,jsx,vue,svelte}"
- id: css_modules
type: glob
patterns: ["**/*.module.css", "**/*.module.scss"]
- id: css_in_js
type: grep
pattern: "(styled\\.|styled\\(|css`|makeStyles|createStyles)"
glob: "**/*.{ts,tsx,js,jsx}"
- id: design_tokens
type: glob
patterns: ["**/tokens.{ts,js,json}", "**/design-tokens.{ts,js,json}", "**/theme.{ts,js}"]
- id: dark_mode
type: grep
pattern: "(dark:|data-theme|ThemeProvider|useTheme|darkMode)"
glob: "**/*.{ts,tsx,js,jsx,css,scss}"
verbosity: count_only
这会揭示:
- 主要样式方案(Tailwind、CSS Modules、CSS-in-JS、原生 CSS)
- 是否存在设计令牌
- 深色模式的实现状态
- 代码库中的一致性
步骤 1.2:检查配置文件
读取现有配置,了解当前设置。
precision_read:
files:
- path: "tailwind.config.js"
extract: content
- path: "tailwind.config.ts"
extract: content
- path: "postcss.config.js"
extract: content
- path: "src/styles/globals.css"
extract: outline
verbosity: minimal
步骤 1.3:分析组件样式模式
读取有代表性的组件,了解相关约定。
precision_read:
files:
- path: "src/components/Button.tsx" # or discovered component
extract: content
- path: "src/components/Card.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
阶段 2:做出决策
选择适合项目需求的样式方案。完整的决策树请参阅 references/styling-comparison.md。
快速决策指南
适用于以下情况:Tailwind CSS
- 希望使用工具类快速构建原型
- 团队间的一致性是优先事项
- 相比完全自由,更偏好设计约束
- 正在构建基于组件的 UI(React、Vue、Svelte)
适用于以下情况:CSS Modules
- 希望样式作用域隔离,且无需增加构建复杂性
- 更喜欢编写传统 CSS
- 需要与现有全局样式共存
- 包体积是主要考量(比 Tailwind 更小)
适用于以下情况:CSS-in-JS
- 需要运行时动态主题
- 希望为样式属性提供 TypeScript 类型
- 需要结合 JS 逻辑实现组件作用域样式
- 正在使用 styled-components 或 Emotion
适用于以下情况:原生 CSS
- 正在构建交互性较少的简单网站
- 希望最大限度地掌控,并尽量减少依赖
- 渐进增强至关重要
- 更喜欢级联和继承模式
有关针对特定框架的详细模式,请参阅 references/styling-comparison.md。
阶段 3:配置设置
步骤 3.1:安装依赖
根据所选方案安装所需的软件包。
Tailwind CSS:
precision_exec:
commands:
- cmd: "npm install -D tailwindcss postcss autoprefixer"
expect:
exit_code: 0
- cmd: "npx tailwindcss init -p"
expect:
exit_code: 0
verbosity: minimal
CSS-in-JS(styled-components):
precision_exec:
commands:
- cmd: "npm install styled-components"
- cmd: "npm install -D @types/styled-components"
verbosity: minimal
步骤 3.2:创建配置
遵循最佳实践编写配置文件。
Tailwind 配置示例:
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class', // or 'media' for system preference
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
// ... full scale
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
},
},
},
plugins: [],
};
export default config;
最佳实践:
- 配置文件使用 TypeScript(确保类型安全)
- 扩展主题,而不是替换默认值
- 使用 CSS 变量表示动态值
- 明确指定内容路径,避免构建速度变慢
- 深色模式使用
class策略(控制力更强)
步骤 3.3:编写配置文件
precision_write:
files:
- path: "tailwind.config.ts"
content: |
import type { Config } from 'tailwindcss';
// ... [full config]
- path: "src/styles/globals.css"
content: |
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
/* ... design tokens */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
verbosity: count_only
阶段 4:设计令牌系统
步骤 4.1:定义令牌结构
创建集中式令牌系统,确保一致性。
令牌类别:
- 颜色:主色、次色、中性色、语义色(成功、错误、警告)
- 排版:字体系列、字号、字重、行高
- 间距:一致的比例(以 4px 为基数,按 8px 递增)
- 阴影:层级系统
- 边框:圆角半径、宽度
- 断点:响应式设计系统
- 动画:时长、缓动函数
实现模式:
// src/styles/tokens.ts
export const tokens = {
colors: {
primary: {
light: '#3b82f6',
DEFAULT: '#2563eb',
dark: '#1d4ed8',
},
semantic: {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
},
},
spacing: {
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
},
typography: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
fontSize: {
xs: ['0.75rem', { lineHeight: '1rem' }],
sm: ['0.875rem', { lineHeight: '1.25rem' }],
base: ['1rem', { lineHeight: '1.5rem' }],
lg: ['1.125rem', { lineHeight: '1.75rem' }],
xl: ['1.25rem', { lineHeight: '1.75rem' }],
},
},
} as const;
export type Tokens = typeof tokens;
// Integration with ThemeProvider (React Context)
type Theme = {
tokens: Tokens;
mode: 'light' | 'dark';
};
// Integration with styled-components
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme extends Tokens {
mode: 'light' | 'dark';
}
}
// Usage in components
import { useTheme } from 'styled-components';
const Button = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing[4]};
`;
步骤 4.2:将令牌集成到 Tailwind 中
// tailwind.config.ts
import { tokens } from './src/styles/tokens';
const config: Config = {
theme: {
extend: {
colors: tokens.colors,
spacing: tokens.spacing,
fontFamily: tokens.typography.fontFamily,
fontSize: tokens.typography.fontSize,
},
},
};
阶段 5:实现深色模式
步骤 5.1:选择深色模式策略
基于类的方式(推荐):
- 更好地控制切换行为
- 可以保留用户偏好
- 适用于 next-themes 或类似库
基于媒体查询的方式:
- 仅遵循系统偏好
- 无需 JS
- 灵活性较低
步骤 5.2:配置 next-themes(React/Next.js)
// src/components/ThemeProvider.tsx
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ReactNode } from 'react';
interface ThemeProviderProps {
children: ReactNode;
}
export function ThemeProvider({ children }: ThemeProviderProps) {
return (
<NextThemesProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}
步骤 5.3:定义深色模式色阶
使用 CSS 变量,实现流畅的主题切换。
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
}
}
最佳实践:
- 使用 HSL 值,以便更轻松地调整
- 保持语义化命名(background、foreground、primary)
- 测试对比度,确保无障碍(WCAG AA:4.5:1)
- 深色模式下避免使用纯黑色(#000),改用深灰色
阶段 6:响应式设计模式
步骤 6.1:制定断点策略
移动优先方法(推荐):
/* Base styles for mobile */
.container {
padding: 1rem;
}
/* Tablet and up */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* Desktop and up */
@media (min-width: 1024px) {
.container {
padding: 3rem;
}
}
Tailwind 断点:
<div className="p-4 md:p-8 lg:p-12">
{/* padding increases with screen size */}
</div>
步骤 6.2:容器查询(现代方式)
使用容器查询实现组件级响应式布局。
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Tailwind 容器查询:
// tailwind.config.ts
plugins: [require('@tailwindcss/container-queries')]
<div className="@container">
<div className="@md:grid @md:grid-cols-2">
{/* Responsive to container, not viewport */}
</div>
</div>
阶段 7:组件变体系统
步骤 7.1:安装 class-variance-authority(CVA)
precision_exec:
commands:
- cmd: "npm install class-variance-authority clsx tailwind-merge"
verbosity: minimal
步骤 7.2:创建 cn 工具函数
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
步骤 7.3:构建变体组件
// src/components/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import type { ButtonHTMLAttributes } from 'react';
const buttonVariants = cva(
// Base styles
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent',
ghost: 'hover:bg-accent hover:text-accent-foreground',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
export function Button({
className,
variant,
size,
...props
}: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
优点:
- 类型安全的变体属性
- 自动合并 Tailwind 类
- 统一的组件 API
- 易于扩展新变体
阶段 8:错误状态样式
步骤 8.1:表单验证状态
// src/components/Input.tsx
import { cva } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const inputVariants = cva(
'w-full rounded-md border px-3 py-2 text-sm transition-colors',
{
variants: {
state: {
default: 'border-input focus:border-primary focus:ring-2 focus:ring-primary/20',
error: 'border-destructive focus:border-destructive focus:ring-2 focus:ring-destructive/20',
success: 'border-green-500 focus:border-green-500 focus:ring-2 focus:ring-green-500/20',
},
},
defaultVariants: {
state: 'default',
},
}
);
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
error?: string;
success?: boolean;
}
export function Input({ error, success, className, ...props }: InputProps) {
const state = error ? 'error' : success ? 'success' : 'default';
return (
<div className="space-y-1">
<input
className={cn(inputVariants({ state }), className)}
aria-invalid={!!error}
aria-describedby={error ? 'input-error' : undefined}
{...props}
/>
{error && (
<p id="input-error" className="text-sm text-destructive" role="alert">
{error}
</p>
)}
</div>
);
}
步骤 8.2:Toast 通知
// src/components/Toast.tsx
import { cva } from 'class-variance-authority';
const toastVariants = cva(
'rounded-lg border p-4 shadow-lg',
{
variants: {
variant: {
default: 'bg-background border-border',
success: 'bg-green-50 border-green-200 text-green-900 dark:bg-green-950 dark:border-green-800 dark:text-green-100',
error: 'bg-red-50 border-red-200 text-red-900 dark:bg-red-950 dark:border-red-800 dark:text-red-100',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900 dark:bg-yellow-950 dark:border-yellow-800 dark:text-yellow-100',
},
},
}
);
步骤 8.3:加载/骨架屏状态
/* Skeleton pulse animation */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.skeleton {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
background-color: hsl(var(--muted));
border-radius: 0.375rem;
}
// src/components/Skeleton.tsx
export function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn('skeleton', className)} {...props} />;
}
// Usage in loading states
export function CardSkeleton() {
return (
<div className="space-y-3">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-20 w-full" />
</div>
);
}
阶段 9:动画模式
步骤 9.1:CSS 过渡(轻量级)
.button {
transition: background-color 200ms ease-in-out;
}
.button:hover {
background-color: var(--primary-hover);
}
Tailwind:
<button className="transition-colors duration-200 hover:bg-primary-hover">
Click me
</button>
步骤 9.2:Framer Motion(高级)
用于复杂动画和手势。
// src/components/AnimatedCard.tsx
import { motion } from 'framer-motion';
export function AnimatedCard() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
<div className="card">Content</div>
</motion.div>
);
}
最佳实践:
- UI 反馈动画时长控制在 300ms 以内
- 使用缓动函数(ease-in-out、ease-out)
- 遵循
prefers-reduced-motion设置 - 避免为会触发布局变化的属性添加动画(width、height)
- 优先使用 transform 和 opacity(由 GPU 加速)
阶段 10:验证
步骤 10.1:运行样式验证脚本
bash scripts/validate-styling.sh .
完整的验证套件请参见 scripts/validate-styling.sh。
步骤 10.2:检查构建输出
验证 CSS 包大小,并确保没有未使用的样式。
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
verbosity: standard
检查项:
- CSS 包大小(Tailwind 压缩后小于 50KB 为佳)
- 没有重复的工具类
- 正确执行 tree-shaking
- 正确生成深色模式类
步骤 10.3:无障碍检查
确保颜色对比度符合 WCAG 标准。
precision_exec:
commands:
- cmd: "npx @axe-core/cli http://localhost:3000"
verbosity: standard
步骤 10.4:视觉回归测试
通过自动化截图对比,发现意外的视觉变化。
使用 Playwright 进行视觉测试:
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
test('button variants match snapshots', async ({ page }) => {
await page.goto('/components/button');
// Take screenshot and compare
await expect(page).toHaveScreenshot('button-variants.png', {
maxDiffPixels: 100,
});
});
test('dark mode toggle', async ({ page }) => {
await page.goto('/');
// Light mode
await expect(page).toHaveScreenshot('home-light.png');
// Toggle to dark
await page.click('[data-testid="theme-toggle"]');
await expect(page).toHaveScreenshot('home-dark.png');
});
Chromatic(用于 Storybook):
# Install and setup
npm install --save-dev chromatic
# Run visual tests
npx chromatic --project-token=<token>
Percy(跨浏览器):
import percySnapshot from '@percy/playwright';
test('responsive layout', async ({ page }) => {
await page.goto('/dashboard');
await percySnapshot(page, 'Dashboard - Desktop');
await page.setViewportSize({ width: 375, height: 667 });
await percySnapshot(page, 'Dashboard - Mobile');
});
步骤 10.5:CSS 清除配置
Tailwind v3+(使用 JIT):
tailwind.config.ts 中的 content 路径即为清除配置。Tailwind 会在生产构建中自动移除未使用的类。
// tailwind.config.ts
export default {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/app/**/*.{js,ts,jsx,tsx}',
],
// JIT mode is default in v3+
};
生产构建优化:
# Build with minification
npx tailwindcss -i ./src/styles/globals.css -o ./dist/output.css --minify
# Check final size
du -h dist/output.css
动态类加入安全列表:
// tailwind.config.ts
export default {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
safelist: [
// Dynamic classes that can't be detected statically
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// Or use patterns
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
},
],
};
监控包大小:
precision_exec:
commands:
- cmd: "npm run build"
- cmd: "ls -lh dist/**/*.css"
verbosity: standard
目标:清除无用样式后,典型 Tailwind 项目的压缩包大小小于 50KB。
常见反模式
不要:
- 在同一组件中混用样式方案(Tailwind + CSS-in-JS)
- 对静态值使用内联样式
- 硬编码颜色/间距(使用设计令牌)
- 忽略深色模式的对比度
- 使用
!important覆盖 Tailwind(应改为解决优先级问题) - 为宽度/高度添加动画(会导致布局抖动)
- 忽略移动优先的响应式设计
- 到处使用像素值(为提升无障碍性,优先使用 rem)
要:
- 选择一种主要的样式方案并保持一致
- 使用 CSS 变量表示动态值
- 从一开始就实现设计令牌
- 测试深色模式下的对比度和可读性
- 遵循 Tailwind 的实用优先理念
- 为提升性能,对 transform 和 opacity 添加动画
- 采用移动优先设计,并针对更大屏幕进行优化
- 使用相对单位(rem、em、%)以便灵活缩放
安全注意事项
CSP(内容安全策略)与样式:
默认安全(构建时):
- Tailwind CSS - 在构建时编译,不会在运行时注入
- CSS Modules - 在构建时编译,符合 CSP 要求
- Vanilla CSS / SCSS - 作为静态文件提供
需要配置 CSP(运行时):
- styled-components - 在运行时注入
<style>标签 - Emotion - 在运行时注入样式
- 内联样式 - 可能需要
unsafe-inline或 nonce
CSP 最佳实践:
// Next.js example with CSP for styled-components
import { getCspNonce } from './lib/csp';
export default function RootLayout({ children }) {
const nonce = getCspNonce();
return (
<html>
<head>
<meta
httpEquiv="Content-Security-Policy"
content={`style-src 'self' 'nonce-${nonce}';`}
/>
</head>
<body>
<StyleSheetManager nonce={nonce}>
{children}
</StyleSheetManager>
</body>
</html>
);
}
防止 CSS 注入:
- 切勿将未经清理的用户输入插入 CSS 值
- 对用户可配置的颜色/主题使用允许列表
- 应用前验证颜色值
// DON'T: Unsafe user input interpolation
const BadButton = ({ userColor }) => (
<div style={{ backgroundColor: userColor }} /> // CSS injection risk!
);
// DO: Validate against allowlist
const ALLOWED_COLORS = ['primary', 'secondary', 'accent'] as const;
type AllowedColor = typeof ALLOWED_COLORS[number];
const SafeButton = ({ color }: { color: AllowedColor }) => (
<div className={`bg-${color}`} />
);
// DO: Validate hex colors
function isValidHexColor(color: string): boolean {
return /^#[0-9A-Fa-f]{6}$/.test(color);
}
快速参考
发现阶段:
discover: { queries: [tailwind, css_modules, css_in_js, tokens, dark_mode], verbosity: count_only }
precision_read: { files: [tailwind.config.ts, globals.css], verbosity: minimal }
配置阶段:
precision_exec: { commands: [{ cmd: "npm install -D tailwindcss postcss autoprefixer" }] }
precision_write: { files: [tailwind.config.ts, globals.css, tokens.ts], verbosity: count_only }
验证阶段:
bash scripts/validate-styling.sh .
npm run build
有关详细的决策树、令牌示例和特定框架的模式,请参阅 references/styling-comparison.md。