重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
styling-system by mgd34msu/goodvibes-plugin
npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill styling-systemscripts/
validate-styling.sh
references/
styling-comparison.md
此技能指导您使用 GoodVibes 精确工具进行 CSS 架构决策和实现。在设置样式基础设施、创建设计系统或实现主题化和响应式模式时,请使用此工作流。
在以下情况下加载此技能:
触发短语:"setup styling"、"add Tailwind"、"implement dark mode"、"design tokens"、"responsive design"、"CSS architecture"、"theme system"。
在做出样式决策之前,先了解项目的当前状态。
使用 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
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
这将揭示:
读取现有配置以了解设置情况。
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
读取代表性组件以了解约定。
precision_read:
files:
- path: "src/components/Button.tsx" # 或已发现的组件
extract: content
- path: "src/components/Card.tsx"
extract: content
output:
max_per_item: 100
verbosity: standard
选择适合您项目需求的样式方法。完整的决策树请参见 references/styling-comparison.md。
在以下情况下使用 Tailwind CSS:
在以下情况下使用 CSS Modules:
在以下情况下使用 CSS-in-JS:
在以下情况下使用原生 CSS:
有关详细的框架特定模式,请参见 references/styling-comparison.md。
根据您选择的方法,安装所需的包。
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
按照最佳实践编写配置文件。
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', // 或 'media' 用于系统偏好设置
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
// ... 完整色阶
900: '#0c4a6e',
},
},
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
},
spacing: {
'18': '4.5rem',
},
},
},
plugins: [],
};
export default config;
最佳实践:
class 策略(控制力更强)precision_write:
files:
- path: "tailwind.config.ts"
content: |
import type { Config } from 'tailwindcss';
// ... [完整配置]
- 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%;
/* ... 设计令牌 */
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
verbosity: count_only
创建一个集中化的令牌系统以确保一致性。
令牌类别:
实现模式:
// 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;
// 与 ThemeProvider 集成(React Context)
type Theme = {
tokens: Tokens;
mode: 'light' | 'dark';
};
// 与 styled-components 集成
import 'styled-components';
declare module 'styled-components' {
export interface DefaultTheme extends Tokens {
mode: 'light' | 'dark';
}
}
// 在组件中使用
import { useTheme } from 'styled-components';
const Button = styled.button`
background: ${props => props.theme.colors.primary};
padding: ${props => props.theme.spacing[4]};
`;
// 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,
},
},
};
基于类(推荐):
基于媒体查询:
// 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>
);
}
使用 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%;
}
}
最佳实践:
移动优先方法(推荐):
/* 移动端基础样式 */
.container {
padding: 1rem;
}
/* 平板及以上 */
@media (min-width: 768px) {
.container {
padding: 2rem;
}
}
/* 桌面及以上 */
@media (min-width: 1024px) {
.container {
padding: 3rem;
}
}
Tailwind 断点:
<div className="p-4 md:p-8 lg:p-12">
{/* 内边距随屏幕尺寸增加 */}
</div>
使用容器查询实现组件级响应式。
.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">
{/* 响应容器,而非视口 */}
</div>
</div>
precision_exec:
commands:
- cmd: "npm install class-variance-authority clsx tailwind-merge"
verbosity: minimal
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 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(
// 基础样式
'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}
/>
);
}
优势:
// 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>
);
}
// 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',
},
},
}
);
/* 骨架屏脉动动画 */
@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} />;
}
// 在加载状态中使用
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>
);
}
.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">
点击我
</button>
用于复杂的动画和手势。
// 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">内容</div>
</motion.div>
);
}
最佳实践:
prefers-reduced-motionbash scripts/validate-styling.sh .
完整的验证套件请参见 scripts/validate-styling.sh。
验证 CSS 包大小并确保没有未使用的样式。
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
verbosity: standard
检查:
确保颜色对比度符合 WCAG 标准。
precision_exec:
commands:
- cmd: "npx @axe-core/cli http://localhost:3000"
verbosity: standard
通过自动化的截图比较来捕获意外的视觉变化。
使用 Playwright 进行视觉测试:
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
test('button variants match snapshots', async ({ page }) => {
await page.goto('/components/button');
// 截图并比较
await expect(page).toHaveScreenshot('button-variants.png', {
maxDiffPixels: 100,
});
});
test('dark mode toggle', async ({ page }) => {
await page.goto('/');
// 浅色模式
await expect(page).toHaveScreenshot('home-light.png');
// 切换到深色
await page.click('[data-testid="theme-toggle"]');
await expect(page).toHaveScreenshot('home-dark.png');
});
Chromatic(用于 Storybook):
# 安装和设置
npm install --save-dev chromatic
# 运行视觉测试
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');
});
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 模式在 v3+ 中是默认的
};
生产构建优化:
# 使用压缩构建
npx tailwindcss -i ./src/styles/globals.css -o ./dist/output.css --minify
# 检查最终大小
du -h dist/output.css
安全列表动态类:
// tailwind.config.ts
export default {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
safelist: [
// 无法静态检测的动态类
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// 或使用模式
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
},
],
};
监控包大小:
precision_exec:
commands:
- cmd: "npm run build"
- cmd: "ls -lh dist/**/*.css"
verbosity: standard
目标:清理后,典型的 Tailwind 项目压缩后 <50KB。
不要:
!important 覆盖 Tailwind(应修复特异性)要:
CSP(内容安全策略)与样式:
默认安全(构建时):
需要 CSP 配置(运行时):
<style> 标签unsafe-inline 或 nonceCSP 最佳实践:
// 使用 styled-components 的 Next.js CSP 示例
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 值中
对用户可配置的颜色/主题使用允许列表
在应用前验证颜色值
// 不要:不安全的用户输入插值 const BadButton = ({ userColor }) => (
<div style={{ backgroundColor: userColor }} /> // CSS 注入风险! );// 要:根据允许列表验证 const ALLOWED_COLORS = ['primary', 'secondary', 'accent'] as const; type AllowedColor = typeof ALLOWED_COLORS[number];
const SafeButton = ({ color }: { color: AllowedColor }) => (
<div className={`bg-${color}`} /> );// 要:验证十六进制颜色 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。
每周安装
47
仓库
GitHub 星标
6
首次出现
2026年2月17日
安全审计
安装于
opencode46
github-copilot46
codex46
kimi-cli46
gemini-cli46
amp46
scripts/
validate-styling.sh
references/
styling-comparison.md
This skill guides you through CSS architecture decisions and implementation using GoodVibes precision tools. Use this workflow when setting up styling infrastructure, creating design systems, or implementing theming and responsive patterns.
Load this skill when:
Trigger phrases: "setup styling", "add Tailwind", "implement dark mode", "design tokens", "responsive design", "CSS architecture", "theme system".
Before making styling decisions, understand the project's current state.
Use discover to find styling patterns across the codebase.
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
What this reveals:
Read existing config to understand the setup.
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
Read representative components to understand conventions.
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
Choose the styling approach that fits your project needs. See references/styling-comparison.md for the complete decision tree.
Use Tailwind CSS when:
Use CSS Modules when:
Use CSS-in-JS when:
Use Vanilla CSS when:
For detailed framework-specific patterns, see references/styling-comparison.md.
Based on your chosen approach, install required packages.
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
Write config files following best practices.
Tailwind Config Example:
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;
Best Practices:
class strategy for dark mode (more control)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
Create a centralized token system for consistency.
Token Categories:
Implementation Pattern:
// 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]};
`;
// 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,
},
},
};
Class-based (Recommended):
Media query-based:
// 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>
);
}
Use CSS variables for seamless theme switching.
/* 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%;
}
}
Best Practices:
Mobile-First Approach (Recommended):
/* 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 Breakpoints:
<div className="p-4 md:p-8 lg:p-12">
{/* padding increases with screen size */}
</div>
Use container queries for component-level responsiveness.
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
Tailwind Container Queries:
// 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>
precision_exec:
commands:
- cmd: "npm install class-variance-authority clsx tailwind-merge"
verbosity: minimal
// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 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}
/>
);
}
Benefits:
// 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>
);
}
// 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',
},
},
}
);
/* 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>
);
}
.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>
For complex animations and gestures.
// 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>
);
}
Best Practices:
prefers-reduced-motionbash scripts/validate-styling.sh .
See scripts/validate-styling.sh for the complete validation suite.
Verify CSS bundle size and ensure no unused styles.
precision_exec:
commands:
- cmd: "npm run build"
expect:
exit_code: 0
verbosity: standard
Check for:
Ensure color contrast meets WCAG standards.
precision_exec:
commands:
- cmd: "npx @axe-core/cli http://localhost:3000"
verbosity: standard
Catch unintended visual changes with automated screenshot comparison.
Playwright with Visual Testing:
// 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 (for Storybook):
# Install and setup
npm install --save-dev chromatic
# Run visual tests
npx chromatic --project-token=<token>
Percy (cross-browser):
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');
});
Tailwind v3+ with JIT: The content paths in tailwind.config.ts serve as the purge configuration. Tailwind automatically removes unused classes in production builds.
// 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+
};
Production Build Optimization:
# Build with minification
npx tailwindcss -i ./src/styles/globals.css -o ./dist/output.css --minify
# Check final size
du -h dist/output.css
Safelist Dynamic Classes:
// 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)/,
},
],
};
Monitor Bundle Size:
precision_exec:
commands:
- cmd: "npm run build"
- cmd: "ls -lh dist/**/*.css"
verbosity: standard
Target: <50KB gzipped for typical Tailwind projects after purging.
DON'T:
!important to override Tailwind (fix specificity instead)DO:
CSP (Content Security Policy) and Styling:
Safe by Default (Build-time):
Requires CSP Configuration (Runtime):
<style> tags at runtimeunsafe-inline or nonceCSP Best Practices:
// 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 Injection Prevention:
Never interpolate unsanitized user input into CSS values
Use allowlists for user-configurable colors/themes
Validate color values before applying
// 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); }
Discovery Phase:
discover: { queries: [tailwind, css_modules, css_in_js, tokens, dark_mode], verbosity: count_only }
precision_read: { files: [tailwind.config.ts, globals.css], verbosity: minimal }
Configuration Phase:
precision_exec: { commands: [{ cmd: "npm install -D tailwindcss postcss autoprefixer" }] }
precision_write: { files: [tailwind.config.ts, globals.css, tokens.ts], verbosity: count_only }
Validation Phase:
bash scripts/validate-styling.sh .
npm run build
For detailed decision trees, token examples, and framework-specific patterns, see references/styling-comparison.md.
Weekly Installs
47
Repository
GitHub Stars
6
First Seen
Feb 17, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode46
github-copilot46
codex46
kimi-cli46
gemini-cli46
amp46
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
127,000 周安装
agent-desktop:macOS AI代理桌面自动化工具,通过无障碍API实现观察与控制
46 周安装
TYPO3 Rector 升级指南:自动化迁移 TYPO3 v14 PHP 代码,告别弃用 API
46 周安装
weapp-vite最佳实践:微信小程序Vite项目配置、分包与AI工作流优化指南
46 周安装
测试报告自动分类技能 - 智能分析失败原因,推荐修复方案
46 周安装
Logo管理技能:品牌Logo自动化获取、存储与规范化处理解决方案
46 周安装
移动开发技能指南:React Native、Flutter、iOS与Android原生开发选择与最佳实践
46 周安装