Appearance
UnoCSS即时原子引擎
概述
UnoCSS是新一代原子化CSS引擎,由Vite和Vue的作者Anthony Fu创建。它采用即时(on-demand)生成的方式,只生成你实际使用的样式,具有极高的性能和灵活性。UnoCSS不仅兼容Tailwind CSS的语法,还提供了更强大的预设系统、变体组、图标集成等特性。本文将全面介绍UnoCSS的使用方法和高级特性。
安装和配置
基础安装
bash
# 使用npm
npm install -D unocss
# 使用pnpm
pnpm add -D unocss
# 使用yarn
yarn add -D unocssVite配置
javascript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import UnoCSS from 'unocss/vite'
export default defineConfig({
plugins: [
react(),
UnoCSS(),
],
})
// uno.config.ts
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss'
export default defineConfig({
presets: [
presetUno(), // Tailwind / Windi CSS兼容预设
presetAttributify(), // 属性化模式
presetIcons({ // 图标预设
scale: 1.2,
cdn: 'https://esm.sh/',
}),
],
})
// main.tsx
import 'uno.css'Next.js配置
javascript
// next.config.js
const UnoCSS = require('@unocss/webpack').default
module.exports = {
webpack: (config) => {
config.plugins.push(
UnoCSS()
)
return config
},
}
// _app.tsx
import 'uno.css'Webpack配置
javascript
// webpack.config.js
const UnoCSS = require('@unocss/webpack').default
module.exports = {
plugins: [
UnoCSS(),
],
}核心特性
即时按需生成
jsx
// UnoCSS只生成你实际使用的样式
function Component() {
return (
<div className="p-4 bg-blue-500 text-white rounded-lg">
UnoCSS
</div>
)
}
// 编译后只生成:
// .p-4 { padding: 1rem; }
// .bg-blue-500 { background-color: rgb(59 130 246); }
// .text-white { color: rgb(255 255 255); }
// .rounded-lg { border-radius: 0.5rem; }
// 未使用的样式不会生成
// .p-8 ❌ 不生成
// .bg-red-500 ❌ 不生成属性化模式
jsx
// 启用属性化模式后
function AttributifyComponent() {
return (
<div
text="white center"
bg="blue-500"
p="4"
rounded="lg"
hover:bg="blue-600"
>
Attributify Mode
</div>
)
}
// 等价于
function TraditionalComponent() {
return (
<div className="text-white text-center bg-blue-500 p-4 rounded-lg hover:bg-blue-600">
Traditional Mode
</div>
)
}
// 属性化 + 分组
function GroupedAttributes() {
return (
<button
btn="~ solid" // 应用btn预设
bg="blue-500 hover:blue-600"
text="white center"
p="x-4 y-2"
rounded="md"
>
Click me
</button>
)
}变体组
jsx
// 传统写法
function Traditional() {
return (
<div className="hover:bg-blue-500 hover:text-white hover:scale-105">
Hover me
</div>
)
}
// 使用变体组
function VariantGroup() {
return (
<div className="hover:(bg-blue-500 text-white scale-105)">
Hover me
</div>
)
}
// 多个变体组
function MultipleVariants() {
return (
<div className="
hover:(bg-blue-500 text-white scale-105)
dark:(bg-gray-800 text-gray-100)
md:(p-8 text-xl)
">
Multiple Variant Groups
</div>
)
}
// 嵌套变体组
function NestedVariants() {
return (
<div className="
dark:(
bg-gray-800
text-gray-100
hover:(bg-gray-700 scale-105)
)
">
Nested Variants
</div>
)
}图标集成
jsx
// 配置图标预设
import { presetIcons } from 'unocss'
export default defineConfig({
presets: [
presetIcons({
collections: {
carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
mdi: () => import('@iconify-json/mdi/icons.json').then(i => i.default),
}
}),
],
})
// 使用图标
function IconExample() {
return (
<div>
{/* Iconify图标 */}
<div className="i-carbon-logo-github text-2xl" />
<div className="i-mdi-heart text-red-500 text-3xl" />
{/* 自定义颜色和大小 */}
<div className="i-carbon-star text-yellow-500 text-4xl" />
{/* 组合使用 */}
<button className="flex items-center gap-2">
<div className="i-carbon-add" />
Add Item
</button>
</div>
)
}
// 自定义图标集合
const customIcons = {
'my-icon': '<svg>...</svg>',
}
export default defineConfig({
presets: [
presetIcons({
collections: {
custom: {
icons: customIcons,
},
},
}),
],
})预设系统
官方预设
typescript
// uno.config.ts
import {
defineConfig,
presetUno, // Tailwind/Windi兼容
presetAttributify, // 属性化模式
presetIcons, // 图标
presetTypography, // 排版
presetWebFonts, // Web字体
presetWind, // Windi CSS预设
presetMini, // 最小预设
} from 'unocss'
export default defineConfig({
presets: [
presetUno(),
presetAttributify(),
presetIcons(),
presetTypography(),
presetWebFonts({
provider: 'google',
fonts: {
sans: 'Inter:400,500,600,700',
mono: 'Fira Code',
},
}),
],
})自定义预设
typescript
// 创建自定义预设
import { Preset } from 'unocss'
const myPreset: Preset = {
name: 'my-preset',
rules: [
// 自定义规则
['custom-rule', { color: 'red' }],
[/^m-(.+)$/, ([, d]) => ({ margin: `${d}px` })],
],
variants: [
// 自定义变体
(matcher) => {
if (!matcher.startsWith('my:'))
return matcher
return {
matcher: matcher.slice(3),
selector: s => `${s}.my-class`,
}
},
],
shortcuts: [
// 快捷方式
['btn', 'px-4 py-2 rounded bg-blue-500 text-white'],
['card', 'p-6 bg-white rounded-lg shadow'],
],
theme: {
colors: {
brand: {
primary: '#3b82f6',
secondary: '#8b5cf6',
},
},
},
}
export default defineConfig({
presets: [
presetUno(),
myPreset,
],
})
// 使用自定义预设
function CustomPresetExample() {
return (
<div>
<button className="btn">Custom Button</button>
<div className="card">Custom Card</div>
<div className="m-20">Custom Margin</div>
<div className="my:custom-rule">Custom Variant</div>
</div>
)
}规则和快捷方式
自定义规则
typescript
// 静态规则
export default defineConfig({
rules: [
['custom-red', { color: 'red' }],
['full-screen', {
width: '100vw',
height: '100vh'
}],
],
})
// 动态规则
export default defineConfig({
rules: [
// 匹配 m-数字
[/^m-(\d+)$/, ([, d]) => ({ margin: `${d}px` })],
// 匹配 text-任意颜色
[/^text-(.+)$/, ([, color]) => ({ color })],
// 匹配 grid-cols-数字
[/^grid-cols-(\d+)$/, ([, d]) => ({
'grid-template-columns': `repeat(${d}, minmax(0, 1fr))`,
})],
// 复杂规则
[/^card-(.+)$/, ([, variant]) => {
const variants = {
primary: { background: '#3b82f6', color: 'white' },
secondary: { background: '#8b5cf6', color: 'white' },
}
return variants[variant] || {}
}],
],
})
// 使用自定义规则
function CustomRules() {
return (
<div>
<div className="custom-red">Red Text</div>
<div className="m-20">20px margin</div>
<div className="text-#ff0000">Custom color</div>
<div className="grid grid-cols-3">Grid</div>
<div className="card-primary">Primary Card</div>
</div>
)
}快捷方式
typescript
// 静态快捷方式
export default defineConfig({
shortcuts: {
'btn': 'px-4 py-2 rounded bg-blue-500 text-white cursor-pointer',
'btn-green': 'px-4 py-2 rounded bg-green-500 text-white cursor-pointer',
'card': 'p-6 bg-white rounded-lg shadow-md',
'container': 'max-w-7xl mx-auto px-4',
},
})
// 动态快捷方式
export default defineConfig({
shortcuts: [
// 数组形式
['btn', 'px-4 py-2 rounded cursor-pointer'],
// 函数形式
[/^btn-(.+)$/, ([, color]) => `px-4 py-2 rounded bg-${color}-500 text-white`],
// 带变体
['btn-with-icon', 'btn flex items-center gap-2'],
],
})
// 嵌套快捷方式
export default defineConfig({
shortcuts: {
'btn-base': 'px-4 py-2 rounded cursor-pointer',
'btn-primary': 'btn-base bg-blue-500 text-white',
'btn-secondary': 'btn-base bg-gray-500 text-white',
},
})
// 使用快捷方式
function Shortcuts() {
return (
<div className="container">
<button className="btn">Button</button>
<button className="btn-green">Green Button</button>
<button className="btn-red">Red Button</button>
<div className="card">Card Content</div>
</div>
)
}主题配置
自定义主题
typescript
// uno.config.ts
export default defineConfig({
theme: {
colors: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
primary: 'var(--color-primary)',
secondary: 'var(--color-secondary)',
},
spacing: {
'xs': '0.5rem',
'sm': '1rem',
'md': '1.5rem',
'lg': '2rem',
'xl': '3rem',
},
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' }],
},
breakpoints: {
'xs': '320px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
},
boxShadow: {
'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
},
},
})
// 使用自定义主题
function ThemedComponent() {
return (
<div className="
bg-brand-500
text-white
p-md
font-sans
text-lg
shadow-md
rounded-lg
">
Themed Component
</div>
)
}暗黑模式
typescript
// uno.config.ts
export default defineConfig({
theme: {
colors: {
primary: {
DEFAULT: '#3b82f6',
dark: '#60a5fa',
},
},
},
presets: [
presetUno({
dark: 'class', // 或 'media'
}),
],
})
// 使用暗黑模式
function DarkMode() {
return (
<div className="
bg-white dark:bg-gray-900
text-gray-900 dark:text-gray-100
p-4
">
<button className="
bg-primary
dark:bg-primary-dark
text-white
px-4 py-2
rounded
">
Theme-aware Button
</button>
</div>
)
}
// 暗黑模式切换
function DarkModeToggle() {
const [isDark, setIsDark] = React.useState(false)
React.useEffect(() => {
if (isDark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}, [isDark])
return (
<button onClick={() => setIsDark(!isDark)}>
{isDark ? '🌞' : '🌙'}
</button>
)
}高级特性
任意值
jsx
// 方括号语法 - 任意值
function ArbitraryValues() {
return (
<div>
{/* 任意颜色 */}
<div className="text-[#1da1f2]">Twitter Blue</div>
<div className="bg-[rgb(59,130,246)]">RGB Color</div>
{/* 任意尺寸 */}
<div className="w-[789px]">Custom Width</div>
<div className="h-[50vh]">Viewport Height</div>
{/* 任意属性值 */}
<div className="text-[length:var(--my-var)]">CSS Variable</div>
<div className="grid-cols-[1fr_2fr_1fr]">Custom Grid</div>
{/* 任意CSS */}
<div className="[mask-image:linear-gradient(#000,transparent)]">
Custom CSS
</div>
</div>
)
}变体
typescript
// 自定义变体
export default defineConfig({
variants: [
// 父元素状态
(matcher) => {
if (!matcher.startsWith('parent-hover:'))
return matcher
return {
matcher: matcher.slice(13),
selector: s => `.parent:hover ${s}`,
}
},
// 数据属性
(matcher) => {
if (!matcher.startsWith('data-active:'))
return matcher
return {
matcher: matcher.slice(12),
selector: s => `${s}[data-active="true"]`,
}
},
// 兄弟元素
(matcher) => {
if (!matcher.startsWith('peer-focus:'))
return matcher
return {
matcher: matcher.slice(11),
selector: s => `.peer:focus ~ ${s}`,
}
},
],
})
// 使用自定义变体
function CustomVariants() {
return (
<div className="parent">
<div className="parent-hover:bg-blue-500">
Hover parent to see effect
</div>
<button data-active="true" className="data-active:bg-green-500">
Active Button
</button>
<input className="peer" type="checkbox" />
<div className="peer-focus:opacity-100 opacity-0">
Checkbox focused
</div>
</div>
)
}Transformers
typescript
// uno.config.ts
import { defineConfig } from 'unocss'
import transformerDirectives from '@unocss/transformer-directives'
import transformerVariantGroup from '@unocss/transformer-variant-group'
import transformerCompileClass from '@unocss/transformer-compile-class'
export default defineConfig({
transformers: [
transformerDirectives(), // @apply指令
transformerVariantGroup(), // 变体组
transformerCompileClass(), // 编译类
],
})
// 使用@apply
<style>
.custom-button {
@apply px-4 py-2 rounded bg-blue-500 text-white;
}
.custom-button:hover {
@apply bg-blue-600;
}
</style>
// 使用变体组
function VariantGroups() {
return (
<div className="hover:(bg-blue-500 text-white scale-105)">
Variant Group
</div>
)
}
// 编译类
function CompiledClass() {
return (
<div className=":uno: px-4 py-2 bg-blue-500 text-white rounded">
This will be compiled to a single class
</div>
)
}实战案例
组件库构建
typescript
// Button组件
export const buttonPreset: Preset = {
name: 'button-preset',
shortcuts: {
'btn': 'inline-flex items-center justify-center px-4 py-2 rounded font-medium transition-colors',
'btn-sm': 'btn px-3 py-1.5 text-sm',
'btn-lg': 'btn px-6 py-3 text-lg',
'btn-primary': 'btn bg-blue-500 text-white hover:bg-blue-600',
'btn-secondary': 'btn bg-gray-500 text-white hover:bg-gray-600',
'btn-outline': 'btn border-2 border-blue-500 text-blue-500 hover:bg-blue-50',
},
}
// Card组件
export const cardPreset: Preset = {
name: 'card-preset',
shortcuts: {
'card': 'bg-white rounded-lg shadow-md p-6',
'card-header': 'border-b pb-4 mb-4',
'card-title': 'text-xl font-semibold',
'card-body': 'text-gray-600',
'card-footer': 'border-t pt-4 mt-4 flex justify-end gap-2',
},
}
// 使用组件预设
function ComponentExample() {
return (
<div className="card">
<div className="card-header">
<h3 className="card-title">Card Title</h3>
</div>
<div className="card-body">
Card content goes here
</div>
<div className="card-footer">
<button className="btn-outline">Cancel</button>
<button className="btn-primary">Confirm</button>
</div>
</div>
)
}响应式设计
jsx
function ResponsiveLayout() {
return (
<div className="
container mx-auto px-4
grid gap-6
grid-cols-1
md:grid-cols-2
lg:grid-cols-3
xl:grid-cols-4
">
{items.map(item => (
<div key={item.id} className="
card
hover:(shadow-lg scale-105)
transition-all
">
<img className="
w-full
h-48
object-cover
rounded-t-lg
" src={item.image} alt={item.title} />
<div className="p-4">
<h3 className="
text-lg font-semibold
mb-2
line-clamp-2
">
{item.title}
</h3>
<p className="
text-gray-600
text-sm
line-clamp-3
">
{item.description}
</p>
</div>
</div>
))}
</div>
)
}性能优化
扫描优化
typescript
// uno.config.ts
export default defineConfig({
// 指定扫描的文件
content: {
filesystem: [
'src/**/*.{js,jsx,ts,tsx}',
],
},
// 排除不需要扫描的文件
exclude: [
'node_modules',
'dist',
'.git',
],
// 自定义提取器
extractors: [
{
extractor: (content) => {
return content.match(/[\w-:[\]]+/g) || []
},
extensions: ['html', 'js', 'jsx', 'ts', 'tsx'],
},
],
})安全列表
typescript
// 保护动态类名
export default defineConfig({
safelist: [
'bg-red-500',
'bg-green-500',
'bg-blue-500',
// 或使用正则
...Array.from({ length: 10 }, (_, i) => `text-${i + 1}xl`),
],
})
// 在代码中使用
function DynamicColors({ color }) {
return (
<div className={`bg-${color}-500`}>
Dynamic Color
</div>
)
}最佳实践
1. 预设组织
typescript
// presets/index.ts
import { Preset } from 'unocss'
import { buttonPreset } from './button'
import { cardPreset } from './card'
import { formPreset } from './form'
export const appPreset: Preset = {
name: 'app-preset',
presets: [
buttonPreset,
cardPreset,
formPreset,
],
}
// uno.config.ts
import { appPreset } from './presets'
export default defineConfig({
presets: [
presetUno(),
appPreset,
],
})2. 类型安全
typescript
// 生成类型定义
// package.json
{
"scripts": {
"uno:types": "unocss --write-transformed"
}
}
// 使用类型安全的类名
type ButtonVariant = 'primary' | 'secondary' | 'outline'
type ButtonSize = 'sm' | 'md' | 'lg'
interface ButtonProps {
variant?: ButtonVariant
size?: ButtonSize
}
function Button({ variant = 'primary', size = 'md', children }: ButtonProps) {
return (
<button className={`btn-${variant} btn-${size}`}>
{children}
</button>
)
}3. 开发体验
typescript
// 启用HMR
export default defineConfig({
// 开发模式配置
envMode: 'dev',
// 启用Inspector
inspector: true,
})
// VS Code扩展
// 安装 "UnoCSS" 扩展获得智能提示总结
UnoCSS即时原子引擎要点:
- 即时生成:按需生成实际使用的样式
- 属性化模式:更灵活的样式编写方式
- 变体组:简化多状态样式
- 图标集成:内置图标系统
- 预设系统:高度可定制
- 规则和快捷方式:强大的扩展能力
- 主题配置:完整的主题系统
- 性能优化:极致的性能表现
UnoCSS是现代化、高性能的原子化CSS解决方案,适合追求极致性能和开发体验的项目。