0
# Rules and Variants
1
2
The rule and variant system in UnoCSS Core provides powerful pattern matching for generating CSS from utility classes. Rules define how utility classes map to CSS properties, while variants add modifiers like pseudo-classes and media queries.
3
4
## Rule System
5
6
### Rule Types
7
8
```typescript { .api }
9
type Rule<Theme extends object = object> = DynamicRule<Theme> | StaticRule;
10
11
type StaticRule = [string, CSSObject | CSSEntries | (CSSValueInput | string)[], RuleMeta?];
12
type DynamicRule<Theme extends object = object> = [RegExp, DynamicMatcher<Theme>, RuleMeta?];
13
14
type DynamicMatcher<Theme extends object = object> = (
15
match: RegExpMatchArray,
16
context: Readonly<RuleContext<Theme>>
17
) =>
18
| Awaitable<CSSValueInput | string | (CSSValueInput | string)[] | undefined>
19
| Generator<CSSValueInput | string | undefined>
20
| AsyncGenerator<CSSValueInput | string | undefined>;
21
```
22
23
### Static Rules
24
25
Static rules provide direct string-to-CSS mapping for exact utility class matches:
26
27
```typescript
28
// Basic static rule
29
['flex', { display: 'flex' }]
30
31
// Static rule with multiple properties
32
['btn-primary', {
33
'background-color': '#3b82f6',
34
'color': 'white',
35
'padding': '0.5rem 1rem',
36
'border-radius': '0.25rem'
37
}]
38
39
// Static rule with CSS entries format
40
['reset', [
41
['margin', '0'],
42
['padding', '0'],
43
['box-sizing', 'border-box']
44
]]
45
```
46
47
### Dynamic Rules
48
49
Dynamic rules use regular expressions to match patterns and generate CSS dynamically:
50
51
```typescript
52
// Numeric spacing rule
53
[/^m-(\d+)$/, ([, num]) => ({ margin: `${num}px` })]
54
55
// Color rule with theme access
56
[/^text-(.+)$/, ([, color], { theme }) => {
57
const colorValue = theme.colors?.[color];
58
if (colorValue) return { color: colorValue };
59
}]
60
61
// Complex responsive rule
62
[/^grid-cols-(\d+)$/, ([, cols]) => ({
63
'display': 'grid',
64
'grid-template-columns': `repeat(${cols}, minmax(0, 1fr))`
65
})]
66
```
67
68
### Rule Context
69
70
```typescript { .api }
71
interface RuleContext<Theme extends object = object> {
72
rawSelector: string;
73
currentSelector: string;
74
generator: UnoGenerator<Theme>;
75
symbols: ControlSymbols;
76
theme: Theme;
77
variantHandlers: VariantHandler[];
78
variantMatch: VariantMatchedResult<Theme>;
79
constructCSS: (body: CSSEntries | CSSObject, overrideSelector?: string) => string;
80
rules?: Rule<Theme>[];
81
shortcuts?: Shortcut<Theme>[];
82
variants?: Variant<Theme>[];
83
}
84
```
85
86
The rule context provides access to:
87
- **rawSelector**: Original utility class name
88
- **currentSelector**: Processed selector after variant matching
89
- **generator**: UnoCSS generator instance
90
- **theme**: Resolved theme object
91
- **constructCSS**: Helper to construct CSS with variant processing
92
93
## Rule Metadata
94
95
```typescript { .api }
96
interface RuleMeta {
97
layer?: string;
98
noMerge?: boolean;
99
sort?: number;
100
autocomplete?: Arrayable<AutoCompleteTemplate>;
101
prefix?: string | string[];
102
internal?: boolean;
103
custom?: Record<string, any>;
104
}
105
```
106
107
Rule metadata controls:
108
- **layer**: CSS layer assignment
109
- **noMerge**: Prevent selector merging optimization
110
- **sort**: Fine-tune rule ordering
111
- **prefix**: Required prefixes for rule matching
112
- **internal**: Hide from user code (shortcuts only)
113
114
## Variant System
115
116
### Variant Types
117
118
```typescript { .api }
119
type Variant<Theme extends object = object> = VariantFunction<Theme> | VariantObject<Theme>;
120
121
type VariantFunction<Theme extends object = object> = (
122
matcher: string,
123
context: Readonly<VariantContext<Theme>>
124
) => Awaitable<string | VariantHandler | VariantHandler[] | undefined>;
125
126
interface VariantObject<Theme extends object = object> {
127
name?: string;
128
match: VariantFunction<Theme>;
129
order?: number;
130
multiPass?: boolean;
131
autocomplete?: Arrayable<AutoCompleteFunction | AutoCompleteTemplate>;
132
}
133
```
134
135
### Variant Functions
136
137
Simple variant functions return a modified selector or handler:
138
139
```typescript
140
// Pseudo-class variant
141
(matcher) => {
142
if (matcher.startsWith('hover:'))
143
return {
144
matcher: matcher.slice(6), // Remove 'hover:' prefix
145
selector: s => `${s}:hover`
146
};
147
}
148
149
// Media query variant
150
(matcher) => {
151
if (matcher.startsWith('sm:'))
152
return {
153
matcher: matcher.slice(3),
154
parent: '@media (min-width: 640px)'
155
};
156
}
157
```
158
159
### Variant Handlers
160
161
```typescript { .api }
162
interface VariantHandler {
163
handle?: (input: VariantHandlerContext, next: (input: VariantHandlerContext) => VariantHandlerContext) => VariantHandlerContext;
164
matcher?: string;
165
order?: number;
166
selector?: (input: string, body: CSSEntries) => string | undefined;
167
body?: (body: CSSEntries) => CSSEntries | undefined;
168
parent?: string | [string, number] | undefined;
169
sort?: number;
170
layer?: string | undefined;
171
}
172
173
interface VariantHandlerContext {
174
prefix: string;
175
selector: string;
176
pseudo: string;
177
entries: CSSEntries;
178
parent?: string;
179
parentOrder?: number;
180
layer?: string;
181
sort?: number;
182
noMerge?: boolean;
183
}
184
```
185
186
## CSS Value Types
187
188
```typescript { .api }
189
type CSSObject = Record<string, string | number | undefined>;
190
type CSSEntry = [string, string | number | undefined, Arrayable<string>?];
191
type CSSEntries = CSSEntry[];
192
type CSSValue = CSSObject | CSSEntries;
193
type CSSValueInput = CSSObjectInput | CSSEntriesInput | CSSValue;
194
```
195
196
## Advanced Rule Examples
197
198
### Theme Integration
199
200
```typescript
201
// Rule using theme values
202
[/^bg-(.+)$/, ([, color], { theme }) => {
203
const value = theme.colors?.[color];
204
if (value) return { 'background-color': value };
205
}]
206
207
// Responsive spacing with theme
208
[/^p-(.+)$/, ([, size], { theme }) => {
209
const value = theme.spacing?.[size];
210
if (value) return { padding: value };
211
}]
212
```
213
214
### Generator Functions
215
216
```typescript
217
// Generator function for multiple CSS values
218
[/^animate-(.+)$/, function* ([, name], { theme }) {
219
const animation = theme.animations?.[name];
220
if (animation) {
221
yield { animation: animation.value };
222
if (animation.keyframes) {
223
yield `@keyframes ${name} { ${animation.keyframes} }`;
224
}
225
}
226
}]
227
```
228
229
### Async Rules
230
231
```typescript
232
// Async rule with external data
233
[/^icon-(.+)$/, async ([, name]) => {
234
const iconData = await fetchIcon(name);
235
return {
236
'background-image': `url(${iconData.url})`,
237
'background-size': 'contain',
238
'background-repeat': 'no-repeat'
239
};
240
}]
241
```
242
243
## Advanced Variant Examples
244
245
### Complex Media Queries
246
247
```typescript
248
const responsiveVariant = (matcher, { theme }) => {
249
const match = matcher.match(/^(\w+):(.+)$/);
250
if (!match) return;
251
252
const [, breakpoint, rest] = match;
253
const size = theme.breakpoints?.[breakpoint];
254
255
if (size) {
256
return {
257
matcher: rest,
258
parent: `@media (min-width: ${size})`
259
};
260
}
261
};
262
```
263
264
### Container Queries
265
266
```typescript
267
const containerVariant = (matcher) => {
268
const match = matcher.match(/^container-(\w+):(.+)$/);
269
if (!match) return;
270
271
const [, size, rest] = match;
272
return {
273
matcher: rest,
274
parent: `@container (min-width: ${size})`
275
};
276
};
277
```
278
279
### Multi-Pass Variants
280
281
```typescript
282
{
283
name: 'important',
284
match: (matcher) => {
285
if (matcher.endsWith('!')) {
286
return {
287
matcher: matcher.slice(0, -1),
288
body: (body) => body.map(([prop, value]) => [prop, `${value} !important`])
289
};
290
}
291
},
292
multiPass: true
293
}
294
```
295
296
## Performance Optimization
297
298
### Static Rule Indexing
299
300
Static rules are automatically indexed in a hash map for O(1) lookup performance:
301
302
```typescript
303
// These rules are indexed for fast lookup
304
['flex', { display: 'flex' }],
305
['block', { display: 'block' }],
306
['hidden', { display: 'none' }]
307
```
308
309
### Dynamic Rule Ordering
310
311
Dynamic rules are processed in definition order, so place more specific patterns first:
312
313
```typescript
314
[
315
// More specific patterns first
316
[/^bg-(red|blue|green)-(\d{3})$/, handler1],
317
[/^bg-(\w+)-(\d+)$/, handler2],
318
[/^bg-(.+)$/, handler3] // Most general last
319
]
320
```
321
322
### Rule Caching
323
324
The generator automatically caches rule matching results to avoid recomputation for repeated utility classes.