CC 咖啡猫的工作空间 Coding Space

CSS 框架

方案分类与选型

组件库风格 vs 原子化风格

维度 组件库风格 原子化风格
代表 Bootstrap, Ant Design, Element Plus Tailwind CSS, UnoCSS
设计理念 封装好的 UI 组件开箱即用 提供最小 CSS 单元,开发者组合构建
开发体验 快速搭建页面,少写样式 在 HTML 中直接写类名,无需切文件
定制性 难深度定制(需覆盖/less 变量) 极高,每一像素都可控
CSS 体积 全量引入通常 ~100-200KB+ 按需生成,生产 ~5-10KB gzipped
学习成本 低(熟悉组件 API 即可) 先高后低(需要记忆类名体系)
设计一致性 组件自带设计语言 依赖设计令牌(design tokens)配置
重构成本 替换组件库 = 重写 UI CSS 类名与 HTML 绑定紧密

适用场景对比

场景 推荐方案 原因
管理后台 / B 端 Ant Design / Element Plus / Bootstrap 表单、表格、弹窗等复杂组件开箱即用
C 端品牌站点 Tailwind CSS 设计令牌统一,高度定制
SaaS 产品 UI Tailwind CSS + 组件库 用 Tailwind 做布局和通用样式,组件库做交互
快速原型 / MVP Bootstrap / UnoCSS Bootstrap 原型快;UnoCSS 灵活且体积小
微前端多应用 CSS Modules / Tailwind CSS Modules 天然隔离;Tailwind 无冲突
要求极致性能 UnoCSS 无解析阶段,即时生成,更小体积
TypeScript 类型安全 Panda CSS / Vanilla Extract 编译时 CSS,TypeScript 编写
设计系统 / 组件库内部 Vanilla Extract / Panda CSS Recipe 模式方便定义组件变体

Tailwind CSS

Utility-first 理念

传统语义化 CSS 的问题:

/* 语义化:.card-title 在全局唯一,心智负担重 */
.card-title {
  font-size: 1.25rem;
  font-weight: 600;
  color: #1a1a2e;
  margin-bottom: 0.5rem;
}
/* 改一次样式:改名 / 加修饰符 / 提权,GSS 持续膨胀 */

Utility-first 用组合类替代:

<!-- 无需命名,直接在 HTML 中描述样式 -->
<h2 class="text-xl font-semibold text-gray-900 mb-2">标题</h2>

解决了什么

  • CSS 不再全局增长(每个新页面不增加一行 CSS)
  • 命名疲劳消失(不用发明 .primary-button-hover-v2 这类名字)
  • 修改样式无副作用(不会意外影响其他地方)
  • 设计约束内置(从 100+ 种灰色中选,而不是任意写 #ddd

配置文件

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  // --- content: JIT 扫描路径 ---
  content: [
    './src/**/*.{js,jsx,ts,tsx,vue}',
    './index.html',
    // 注意:动态类名必须完整出现在模板文件中
    // ❌ `bg-${color}-500` 会被识别失败,JIT 无法预知 color 值
    // ✅ 写成完整字符串:`bg-red-500`、`bg-blue-500`
  ],

  // --- theme: 设计令牌 ---
  theme: {
    // 覆盖默认值
    screens: {
      'sm': '640px',
      'md': '768px',
      'lg': '1024px',
      'xl': '1280px',
      '2xl': '1536px',
    },
    colors: {
      brand: {
        50: '#eff6ff',
        500: '#3b82f6',
        900: '#1e3a5f',
      },
    },
    spacing: {
      0: '0',
      1: '0.25rem',
      4: '1rem',
      18: '4.5rem',   // 扩展 18 → 4.5rem
    },

    // extend: 在默认基础上追加
    extend: {
      fontFamily: {
        display: ['"Playfair Display"', 'serif'],
      },
      animation: {
        'fade-in': 'fadeIn 0.3s ease-in-out',
      },
      keyframes: {
        fadeIn: {
          '0%': { opacity: '0' },
          '100%': { opacity: '1' },
        },
      },
    },
  },

  // --- plugins: 社区或自定义插件 ---
  plugins: [
    require('@tailwindcss/forms'),      // 表单样式重置
    require('@tailwindcss/typography'),  // 文章排版(prose 类)
    require('@tailwindcss/aspect-ratio'),
    // 自定义插件
    function({ addUtilities }) {
      addUtilities({
        '.scrollbar-hide': {
          'scrollbar-width': 'none',
          '&::-webkit-scrollbar': { display: 'none' },
        },
      });
    },
  ],
};

响应式设计

断点前缀(移动优先,未加前缀 = 移动端样式):

<!-- 移动:单列;md:两列;lg:三列 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <div class="p-4">Card</div>
</div>

<!-- 文本随断点变化 -->
<p class="text-sm md:text-base lg:text-lg">响应式文本</p>

<!-- 显示/隐藏 -->
<div class="hidden md:block">桌面端可见</div>
<div class="block md:hidden">移动端可见</div>

container 类

<!-- 自动居中 + 响应式 max-width -->
<div class="container mx-auto px-4">
  <!-- 内容 -->
</div>
<!-- 默认 max-width 与各断点一致(sm:640px, md:768px ...) -->

Dark Mode

策略选择

// tailwind.config.js
export default {
  darkMode: 'class',   // 'media' | 'class' | 'selector'
};
策略 原理 适用
media 跟随系统 prefers-color-scheme: dark 简单博客、无切换按钮
class <html> 上切换 .dark 需要用户切换主题、支持三种模式
selector (v3.4+) 自定义选择器,如 [data-mode="dark"] 非标准 class 命名
<!-- class 策略:dark: 变体 -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
  <h1 class="text-2xl font-bold">标题</h1>
</div>

<!-- 使用 JS 切换 -->
<script>
  document.documentElement.classList.toggle('dark');
  // 或结合 localStorage 持久化
</script>

状态变体

<!-- 基础交互 -->
<button class="bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-300
               active:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed">
  提交
</button>

<!-- group-hover: 父容器悬停时子元素变化 -->
<div class="group relative cursor-pointer">
  <img src="thumb.jpg" class="transition-transform group-hover:scale-105" />
  <div class="opacity-0 group-hover:opacity-100 transition-opacity">
    覆盖层
  </div>
</div>

<!-- peer-*: 相邻兄弟元素状态变化 -->
<div class="flex items-center gap-2">
  <input type="checkbox" class="peer" id="agree" />
  <label for="agree" class="peer-checked:text-green-600">同意协议</label>
  <!-- peer 基于 DOM 位置,前一个兄弟元素的状态影响后一个 -->
</div>

<!-- 更多变体 -->
<!-- odd: / even: / first: / last: / visited: / required: / invalid: / placeholder: -->

性能优化(JIT 引擎)

JIT 原理

传统 Tailwind(v2): 预生成全量数千个类 → 100MB+ CSS → PurgeCSS 修剪 → ~10KB
JIT 引擎(v3+):     content 扫描 → 仅生成文件中出现的类 → 即时输出 ~10KB
  • 无全量预生成步骤,构建速度与项目大小无关
  • 开发环境按需即时编译,改动即生效
  • 生产构建自动 tree-shake 未使用的类

content 配置最佳实践

// ❌ 错误:路径不够广,JIT 遗漏类名
content: ['./src/**/*.js'],

// ❌ 错误:动态拼接,JIT 无法静态分析
<div class={`bg-${color}-500`} />

// ✅ 正确:完整类名字符串
content: ['./src/**/*.{js,ts,jsx,tsx,vue,html}'],
// ✅ 安全动态写法:列出所有可能值
const colors = ['red', 'blue', 'green'];
colors.map(c => `bg-${c}-500`);

生产体积:经过 JIT + gzip,典型项目最终 CSS 在 5-10KB,远小于 Bootstrap 的 ~120KB 全量加载。

@apply 与 @layer

@apply:提取重复的 utility 组合(不要滥用,否则又回到语义化 CSS 的老路):

/* 仅在真的重复很多次且不是变体时使用 */
.btn-primary {
  @apply px-4 py-2 bg-blue-500 text-white rounded-lg
         hover:bg-blue-600 focus:ring-2 focus:ring-blue-300
         transition-colors duration-200;
}

.card-base {
  @apply bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200;
}
<button class="btn-primary">按钮</button>

@layer 与层级顺序

@tailwind base;       /* 基础样式 (normalize + 设计令牌变量) */
@tailwind components; /* @apply 组件的样式 */
@tailwind utilities;  /* utility 类 (hover/focus/dark: 变体) */

/* 自定义样式需指定层级以保证覆盖顺序正确 */
@layer base {
  h1 { @apply text-3xl font-bold; }
}

@layer components {
  .card { @apply p-6 rounded-xl shadow; }
}

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

层级优先级!important 标记:base < components < utilities。所以 utility 始终能覆盖组件样式。

警告:Vue SFC 中 @apply 不支持 @media@screen 指令,也不能混合 dark: 变体。

任意值

当设计令牌不够用时,用任意值语法突破约束:

<!-- 宽度/高度 -->
<div class="w-[300px] h-[200px]">固定尺寸</div>

<!-- 颜色 -->
<div class="bg-[#1da1f2] text-[#ffd700]">任意颜色</div>

<!-- 网格 -->
<div class="grid-cols-[1fr_2fr]">自定义网格比例</div>

<!-- 阴影 -->
<div class="shadow-[0_4px_14px_0_rgba(0,0,0,0.1)]">自定义阴影</div>

<!-- 计算 -->
<div class="top-[calc(100%-4rem)]">表达式</div>

<!-- CSS 变量 -->
<div class="text-[var(--brand-color)]">引用 CSS 变量</div>

与组件框架集成

React:clsx / cn 模式

// utils/cn.ts (推荐搭配 tailwind-merge)
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
// cn 自动处理条件类名 + 合并冲突
<button className={cn(
  'px-4 py-2 rounded-lg font-medium transition-colors',
  variant === 'primary' && 'bg-blue-500 text-white hover:bg-blue-600',
  variant === 'secondary' && 'bg-gray-200 text-gray-800 hover:bg-gray-300',
  disabled && 'opacity-50 cursor-not-allowed',
  className  // 外部传入的类名
)}>
  {children}
</button>

CVA(Class Variant Authority)模式

import { cva, type VariantProps } from 'class-variance-authority';

const button = cva('rounded-lg font-medium transition-colors', {
  variants: {
    variant: {
      primary: 'bg-blue-500 text-white hover:bg-blue-600',
      secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
      ghost: 'hover:bg-gray-100',
    },
    size: {
      sm: 'px-3 py-1.5 text-sm',
      md: 'px-4 py-2 text-base',
      lg: 'px-6 py-3 text-lg',
    },
  },
  defaultVariants: {
    variant: 'primary',
    size: 'md',
  },
});

// 类型安全的 props
type ButtonProps = VariantProps<typeof button> & {
  children: React.ReactNode;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

function Button({ variant, size, className, ...props }: ButtonProps) {
  return <button className={button({ variant, size, className })} {...props} />;
}

Vue:useCssModule 与 class 绑定

<template>
  <button
    :class="cn(
      'px-4 py-2 rounded-lg',
      $attrs.class,
    )"
    v-bind="$attrs"
  >
    <slot />
  </button>
</template>

<script setup lang="ts">
import { cn } from '@/utils/cn';
</script>

UnoCSS

按需生成

与 Tailwind JIT 的对比:

特性 Tailwind JIT UnoCSS
生成方式 解析 content → 匹配正则 → 生成 CSS 解析 HTML → 匹配规则 → 即时生成
预热时间 首次启动 ~300-500ms(解析配置+扫文件) 几乎 0 预热
热更新 每次改配置重新编译 规则引擎即时响应
规则引擎 固定规则集(基于配置) 动态规则(预设+自定义规则函数)
体积 ~5-10KB gzipped ~2-5KB gzipped(极轻量)
// uno.config.ts
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss';

export default defineConfig({
  presets: [
    presetUno(),           // Tailwind/Windi CSS 兼容预设
    presetAttributify(),   // Attributify 模式
    presetIcons(),         // 图标预设
  ],
  // 自定义规则
  rules: [
    [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  ],
  shortcuts: {
    'btn': 'px-4 py-2 rounded-lg bg-blue-500 text-white hover:bg-blue-600',
  },
});

预设系统

预设 功能 体积
presetUno Tailwind/Windi 兼容 API(默认) ~3KB
presetAttributify 属性化 HTML 写法 ~0.3KB
presetIcons Iconify 图标 + CSS 图标 ~0.5KB
presetTypography 文章排版(类似 @tailwindcss/typography ~2KB
presetWebFonts Google Fonts / 自定义字体加载 ~0.3KB
presetWind Windi CSS 完全兼容 ~4KB
presetMini 最小化预设(仅基础 margin/padding/flex/grid) ~1KB

Attributify 模式

<!-- 传统 utility 写法 -->
<button class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
  按钮
</button>

<!-- Attributify 模式(需 presetAttributify) -->
<button bg="blue-500" text="white" p="x-4 y-2" rounded="lg" hover="bg-blue-600">
  按钮
</button>

<!-- 分组语法 -->
<div flex="~ col" p="4" border="2 rounded gray-200">
  <span text="sm gray-500">标签</span>
</div>

优点:HTML 更结构化,属性和值分离,可读性提升。 缺点:需额外预设;部分编辑器插件支持不足。

图标方案

通过 presetIcons + Iconify(100,000+ 图标),无需手动导入:

<!-- 直接使用 Iconify 图标 i-{集合名}-{图标名} -->
<span class="i-carbon-sun dark:i-carbon-moon text-2xl"></span>
<span class="i-logos-react w-8 h-8"></span>
<span class="i-twemoji-flag-china w-6 h-6"></span>

<!-- 图标文字混排 -->
<button class="flex items-center gap-2 px-4 py-2">
  <span class="i-carbon-add"></span>
  新建
</button>

原理:UnoCSS 在构建时从 Iconify 数据集提取用到的 SVG 作为 CSS background(或内联),不产生网络请求。

与 Tailwind 对比

对比项 Tailwind CSS UnoCSS
成熟度 社区最大、生态最丰富 较新、但增长迅速
性能 快(JIT) 更快(规则引擎)
配置灵活度 固定 schema,extend 扩展 动态规则函数,无限自定义
预设丰富度 官方 + 社区插件 预设体系,内置图标和 attributify
文档质量 极好(官方文档 + playground) 好(较新、中文支持好)
IDE 支持 全套(VSCode/WebStorm/IntelliJ) VSCode 插件可用,其他有限
学习曲线 中等(需记住类名) 类似 Tailwind(兼容其类名)
SSR/SSG 兼容 通过 content 配置 构建时扫描 HTML
图标方案 需额外库(Heroicons + @heroicons/react) 内置 presetIcons + Iconify

选择建议:如果团队熟悉 Tailwind 且需要稳定生态,选 Tailwind;如果追求极致性能、灵活性和图标方案,选 UnoCSS。


其他方案概述

Bootstrap

特性 说明
栅格系统 .container > .row > .col-*-*,12 列弹性栅格,5 个断点
组件库 导航栏、轮播、模态框、表单、卡片等 50+ 组件(需 JS)
Utility API v5 引入,可用 $utilities: () map 自定义 utility 类
定制方式 SCSS 变量覆盖 $primary $border-radius
体积 全量 ~120KB gzipped(JS + CSS),Tree-shake 困难
适用场景 快速原型、内部系统、对设计无特别要求的团队
<!-- Bootstrap 典型用法 -->
<div class="container">
  <div class="row">
    <div class="col-12 col-md-6 col-lg-4">
      <div class="card">
        <div class="card-body">
          <h5 class="card-title">卡片标题</h5>
          <p class="card-text">内容</p>
          <a href="#" class="btn btn-primary">按钮</a>
        </div>
      </div>
    </div>
  </div>
</div>

CSS-in-JS

方案 运行时 类型安全 体积 代表用法
styled-components 运行时注入 可选 ~15KB gzipped 模板字面量
Emotion 运行时注入 可选 ~10KB gzipped css prop
Panda CSS 编译时(零运行时) 原生 ~0KB runtime Recipe / sva
Vanilla Extract 编译时(零运行时) 原生 ~0KB runtime style / recipe / sprinkles
Linaria 编译时提取 ~0KB runtime 模板字面量编译时

styled-components(运行时)

import styled from 'styled-components';

const Button = styled.button<{ $variant: 'primary' | 'secondary' }>`
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  font-weight: 500;
  background: ${props => props.$variant === 'primary' ? '#3b82f6' : '#e5e7eb'};
  color: ${props => props.$variant === 'primary' ? 'white' : '#374151'};

  &:hover {
    opacity: 0.9;
  }
`;

// 使用
<Button $variant="primary">点击</Button>

运行时 CSS-in-JS 的问题

  • 增加 JS bundle 体积(~10-15KB)
  • SSR 需要额外配置(收集样式注入 HTML)
  • 运行时解析标签模板字面量有性能开销
  • Suspense / Concurrency 模式下可能出现样式闪烁

Panda CSS(编译时零运行时)

// 定义 recipes
import { cva } from '@/styled-system/css';

const button = cva({
  base: {
    display: 'inline-flex',
    alignItems: 'center',
    borderRadius: 'lg',
    fontWeight: 'medium',
  },
  variants: {
    variant: {
      primary: { bg: 'blue.500', color: 'white' },
      secondary: { bg: 'gray.200', color: 'gray.800' },
    },
    size: {
      sm: { px: '3', py: '1.5', fontSize: 'sm' },
      md: { px: '4', py: '2', fontSize: 'base' },
    },
  },
});

// 使用 ← 编译时生成原子化 class,运行时无开销
<button className={button({ variant: 'primary', size: 'md' })} />

Vanilla Extract(编译时零运行时)

// styles.css.ts (TypeScript 文件编译为 CSS)
import { style, recipe } from '@vanilla-extract/css';
import { sprinkles } from './sprinkles.css';

export const container = style([
  sprinkles({ padding: 'medium', background: 'surface' }),
  {
    maxWidth: '1200px',
    margin: '0 auto',
  },
]);

export const button = recipe({
  base: sprinkles({ borderRadius: 'md' }),
  variants: {
    color: {
      primary: sprinkles({ background: 'brand', color: 'white' }),
      secondary: sprinkles({ background: 'neutral' }),
    },
  },
});

CSS Modules

/* Card.module.css */
.card {
  background: white;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.title {
  font-size: 1.25rem;
  font-weight: 600;
}

/* composes: 组合其他类(类似 @extend) */
.errorCard {
  composes: card;
  border: 2px solid #ef4444;
}
// React 中导入
import styles from './Card.module.css';

function Card({ title, children }) {
  return (
    <div className={styles.card}>
      <h3 className={styles.title}>{title}</h3>
      {children}
    </div>
  );
}

CSS Modules 特点

  • 自动生成唯一哈希类名(.Card_card_abc123),天然作用域隔离
  • 无运行时开销(编译时处理)
  • 与全局 CSS / Tailwind 混用策略:
// 混用:全局 utility + 局部样式
<div className={cn(
  styles.card,
  'p-4 shadow-md',  // Tailwind 全局 utility
  className,         // 外部传入的自定义
)}>

适用场景:微前端、多团队协作(防止样式冲突)、已有项目渐进迁移。


选型建议

需求 推荐 说明
快速搭建后台管理 Ant Design / Element Plus / Naive UI 组件丰富,开箱即用,少写样式
快速原型 / MVP Bootstrap / UnoCSS + 预设 Bootstrap 原型快;UnoCSS 灵活体积小
C 端高度定制设计 Tailwind CSS 设计令牌 + utility-first,每一像素可控
追求极致性能 UnoCSS 最小化 CSS 体积 + 极速按需生成
全栈 + TypeScript Panda CSS / Vanilla Extract 编译时 CSS、类型安全、零运行时
微前端 / 多人协作 CSS Modules 编译时作用域隔离,天然防冲突
设计系统团队 Vanilla Extract / Tailwind + CVA Recipe 模式 + 强类型约束
现有项目渐进改造 Tailwind CSS + CSS Modules 全局 utility + 局部作用域混用