TailwindCSS技能Skill tailwind-css

Tailwind CSS 配置和自定义设计系统专家,提供组件样式模式和暗色模式实现

前端开发 0 次安装 0 次浏览 更新于 2/26/2026

Tailwind CSS 技能

专家协助配置 Tailwind CSS,自定义设计系统,插件开发,组件样式模式。

能力

  • 为各种框架配置 Tailwind CSS
  • 创建自定义设计令牌和主题
  • 构建可重用的组件模式
  • 开发自定义 Tailwind 插件
  • 实现暗色模式和主题
  • 优化生产构建

使用方法

当你需要:

  • 在项目中设置 Tailwind CSS
  • 创建自定义设计系统
  • 构建组件样式模式
  • 实现主题和暗色模式
  • 优化 Tailwind 配置

输入

参数 类型 必需 描述
framework string react, nextjs, vue, vanilla
features array dark-mode, custom-colors, typography, forms, animations
designTokens object 自定义颜色,间距,字体
plugins array 要包含的 Tailwind 插件

配置示例

{
  "framework": "nextjs",
  "features": ["dark-mode", "typography", "forms", "animations"],
  "designTokens": {
    "colors": {
      "primary": "#3B82F6",
      "secondary": "#10B981"
    },
    "fontFamily": {
      "sans": "Inter",
      "mono": "JetBrains Mono"
    }
  },
  "plugins": ["@tailwindcss/typography", "@tailwindcss/forms"]
}

输出结构

project/
├── tailwind.config.ts          # 主配置
├── postcss.config.js           # PostCSS 配置
├── app/
│   └── globals.css             # 全局样式和层
├── lib/
│   └── tailwind/
│       ├── plugins/
│       │   └── custom-plugin.ts
│       └── presets/
│           └── design-system.ts
└── components/
    └── ui/
        ├── button.tsx          # 带有变体的组件
        └── card.tsx

生成的代码模式

Tailwind 配置

// tailwind.config.ts
import type { Config } from 'tailwindcss';
import typography from '@tailwindcss/typography';
import forms from '@tailwindcss/forms';
import animate from 'tailwindcss-animate';

const config: Config = {
  darkMode: 'class',
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#eff6ff',
          100: '#dbeafe',
          200: '#bfdbfe',
          300: '#93c5fd',
          400: '#60a5fa',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8',
          800: '#1e40af',
          900: '#1e3a8a',
          950: '#172554',
        },
        secondary: {
          50: '#ecfdf5',
          100: '#d1fae5',
          200: '#a7f3d0',
          300: '#6ee7b7',
          400: '#34d399',
          500: '#10b981',
          600: '#059669',
          700: '#047857',
          800: '#065f46',
          900: '#064e3b',
          950: '#022c22',
        },
      },
      fontFamily: {
        sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
        mono: ['var(--font-jetbrains-mono)', 'monospace'],
      },
      animation: {
        'fade-in': 'fadeIn 0.5s ease-in-out',
        'slide-up': 'slideUp 0.3s ease-out',
        'spin-slow': 'spin 3s linear infinite',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
        slideUp: {
          '0%': { transform: 'translateY(10px)', opacity: '0' },
          '100%': { transform: 'translateY(0)', opacity: '1' },
        },
      },
      spacing: {
        '18': '4.5rem',
        '88': '22rem',
        '128': '32rem',
      },
      borderRadius: {
        '4xl': '2rem',
      },
    },
  },
  plugins: [typography, forms, animate],
};

export default config;

全局样式

/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;
    --card: 0 0% 100%;
    --card-foreground: 222.2 84% 4.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 222.2 84% 4.9%;
    --primary: 221.2 83.2% 53.3%;
    --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: 221.2 83.2% 53.3%;
    --radius: 0.5rem;
  }

  .dark {
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;
    --card: 222.2 84% 4.9%;
    --card-foreground: 210 40% 98%;
    --popover: 222.2 84% 4.9%;
    --popover-foreground: 210 40% 98%;
    --primary: 217.2 91.2% 59.8%;
    --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: 224.3 76.3% 48%;
  }
}

@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
    font-feature-settings: "rlig" 1, "calt" 1;
  }
}

@layer components {
  .btn {
    @apply inline-flex items-center justify-center rounded-md text-sm font-medium
           transition-colors focus-visible:outline-none focus-visible:ring-2
           focus-visible:ring-ring focus-visible:ring-offset-2
           disabled:opacity-50 disabled:pointer-events-none;
  }

  .btn-primary {
    @apply btn bg-primary text-primary-foreground hover:bg-primary/90;
  }

  .btn-secondary {
    @apply btn bg-secondary text-secondary-foreground hover:bg-secondary/80;
  }

  .btn-outline {
    @apply btn border border-input bg-background hover:bg-accent
           hover:text-accent-foreground;
  }

  .btn-ghost {
    @apply btn hover:bg-accent hover:text-accent-foreground;
  }

  .input {
    @apply flex h-10 w-full rounded-md border border-input bg-background px-3 py-2
           text-sm ring-offset-background file:border-0 file:bg-transparent
           file:text-sm file:font-medium placeholder:text-muted-foreground
           focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
           focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50;
  }

  .card {
    @apply rounded-lg border bg-card text-card-foreground shadow-sm;
  }
}

@layer utilities {
  .text-balance {
    text-wrap: balance;
  }

  .animate-in {
    animation-duration: 150ms;
    animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
    animation-fill-mode: both;
  }

  .fade-in {
    animation-name: fadeIn;
  }

  .slide-in-from-top {
    --tw-enter-translate-y: -100%;
    animation-name: slideInFromTop;
  }
}

带变体的按钮组件

// components/ui/button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-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 hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      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',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = 'Button';

export { Button, buttonVariants };

自定义插件

// lib/tailwind/plugins/custom-plugin.ts
import plugin from 'tailwindcss/plugin';

export const customPlugin = plugin(
  function ({ addUtilities, addComponents, matchUtilities, theme }) {
    // 添加自定义工具类
    addUtilities({
      '.text-shadow': {
        textShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
      },
      '.text-shadow-md': {
        textShadow: '0 4px 8px rgba(0, 0, 0, 0.12)',
      },
      '.text-shadow-lg': {
        textShadow: '0 8px 16px rgba(0, 0, 0, 0.15)',
      },
      '.text-shadow-none': {
        textShadow: 'none',
      },
    });

    // 添加自定义组件
    addComponents({
      '.skeleton': {
        animation: 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
        backgroundColor: theme('colors.muted'),
        borderRadius: theme('borderRadius.md'),
      },
    });

    // 添加动态工具类
    matchUtilities(
      {
        'text-shadow': (value) => ({
          textShadow: value,
        }),
      },
      { values: theme('textShadow') }
    );
  },
  {
    theme: {
      textShadow: {
        sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
        DEFAULT: '0 2px 4px rgba(0, 0, 0, 0.1)',
        md: '0 4px 8px rgba(0, 0, 0, 0.12)',
        lg: '0 8px 16px rgba(0, 0, 0, 0.15)',
      },
    },
  }
);

实用工具函数

// lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

暗色模式实现

主题提供器

// components/theme-provider.tsx
'use client';

import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}

主题切换

// components/theme-toggle.tsx
'use client';

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-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">切换主题</span>
    </Button>
  );
}

依赖

{
  "dependencies": {
    "clsx": "^2.1.0",
    "tailwind-merge": "^2.5.0",
    "class-variance-authority": "^0.7.0"
  },
  "devDependencies": {
    "tailwindcss": "^3.4.0",
    "postcss": "^8.4.0",
    "autoprefixer": "^10.4.0",
    "@tailwindcss/typography": "^0.5.0",
    "@tailwindcss/forms": "^0.5.0",
    "tailwindcss-animate": "^1.0.0"
  }
}

工作流程

  1. 安装 Tailwind - 与框架一起设置
  2. 配置主题 - 颜色,字体,间距
  3. 创建 CSS 层 - 基础,组件,工具类
  4. 构建组件 - 使用 CVA 构建变体
  5. 实现暗色模式 - CSS 变量 + 提供器
  6. 优化生产 - 清除未使用的样式

应用的最佳实践

  • 使用 CSS 变量进行主题化
  • 使用类方差权威进行变体
  • 使用 Tailwind 合并解决类名冲突
  • 语义化颜色命名
  • 移动优先的响应式设计
  • 可访问的焦点状态

参考

目标流程

  • design-system-setup
  • component-styling
  • theming-dark-mode
  • responsive-design
  • accessibility-styling