or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

config.mdextraction.mdgenerator.mdindex.mdrules-variants.mdtypes.mdutilities.md

rules-variants.mddocs/

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.