The core atomic CSS engine for UnoCSS that generates CSS on-demand without any presets or default utilities.
—
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.
type Rule<Theme extends object = object> = DynamicRule<Theme> | StaticRule;
type StaticRule = [string, CSSObject | CSSEntries | (CSSValueInput | string)[], RuleMeta?];
type DynamicRule<Theme extends object = object> = [RegExp, DynamicMatcher<Theme>, RuleMeta?];
type DynamicMatcher<Theme extends object = object> = (
match: RegExpMatchArray,
context: Readonly<RuleContext<Theme>>
) =>
| Awaitable<CSSValueInput | string | (CSSValueInput | string)[] | undefined>
| Generator<CSSValueInput | string | undefined>
| AsyncGenerator<CSSValueInput | string | undefined>;Static rules provide direct string-to-CSS mapping for exact utility class matches:
// Basic static rule
['flex', { display: 'flex' }]
// Static rule with multiple properties
['btn-primary', {
'background-color': '#3b82f6',
'color': 'white',
'padding': '0.5rem 1rem',
'border-radius': '0.25rem'
}]
// Static rule with CSS entries format
['reset', [
['margin', '0'],
['padding', '0'],
['box-sizing', 'border-box']
]]Dynamic rules use regular expressions to match patterns and generate CSS dynamically:
// Numeric spacing rule
[/^m-(\d+)$/, ([, num]) => ({ margin: `${num}px` })]
// Color rule with theme access
[/^text-(.+)$/, ([, color], { theme }) => {
const colorValue = theme.colors?.[color];
if (colorValue) return { color: colorValue };
}]
// Complex responsive rule
[/^grid-cols-(\d+)$/, ([, cols]) => ({
'display': 'grid',
'grid-template-columns': `repeat(${cols}, minmax(0, 1fr))`
})]interface RuleContext<Theme extends object = object> {
rawSelector: string;
currentSelector: string;
generator: UnoGenerator<Theme>;
symbols: ControlSymbols;
theme: Theme;
variantHandlers: VariantHandler[];
variantMatch: VariantMatchedResult<Theme>;
constructCSS: (body: CSSEntries | CSSObject, overrideSelector?: string) => string;
rules?: Rule<Theme>[];
shortcuts?: Shortcut<Theme>[];
variants?: Variant<Theme>[];
}The rule context provides access to:
interface RuleMeta {
layer?: string;
noMerge?: boolean;
sort?: number;
autocomplete?: Arrayable<AutoCompleteTemplate>;
prefix?: string | string[];
internal?: boolean;
custom?: Record<string, any>;
}Rule metadata controls:
type Variant<Theme extends object = object> = VariantFunction<Theme> | VariantObject<Theme>;
type VariantFunction<Theme extends object = object> = (
matcher: string,
context: Readonly<VariantContext<Theme>>
) => Awaitable<string | VariantHandler | VariantHandler[] | undefined>;
interface VariantObject<Theme extends object = object> {
name?: string;
match: VariantFunction<Theme>;
order?: number;
multiPass?: boolean;
autocomplete?: Arrayable<AutoCompleteFunction | AutoCompleteTemplate>;
}Simple variant functions return a modified selector or handler:
// Pseudo-class variant
(matcher) => {
if (matcher.startsWith('hover:'))
return {
matcher: matcher.slice(6), // Remove 'hover:' prefix
selector: s => `${s}:hover`
};
}
// Media query variant
(matcher) => {
if (matcher.startsWith('sm:'))
return {
matcher: matcher.slice(3),
parent: '@media (min-width: 640px)'
};
}interface VariantHandler {
handle?: (input: VariantHandlerContext, next: (input: VariantHandlerContext) => VariantHandlerContext) => VariantHandlerContext;
matcher?: string;
order?: number;
selector?: (input: string, body: CSSEntries) => string | undefined;
body?: (body: CSSEntries) => CSSEntries | undefined;
parent?: string | [string, number] | undefined;
sort?: number;
layer?: string | undefined;
}
interface VariantHandlerContext {
prefix: string;
selector: string;
pseudo: string;
entries: CSSEntries;
parent?: string;
parentOrder?: number;
layer?: string;
sort?: number;
noMerge?: boolean;
}type CSSObject = Record<string, string | number | undefined>;
type CSSEntry = [string, string | number | undefined, Arrayable<string>?];
type CSSEntries = CSSEntry[];
type CSSValue = CSSObject | CSSEntries;
type CSSValueInput = CSSObjectInput | CSSEntriesInput | CSSValue;// Rule using theme values
[/^bg-(.+)$/, ([, color], { theme }) => {
const value = theme.colors?.[color];
if (value) return { 'background-color': value };
}]
// Responsive spacing with theme
[/^p-(.+)$/, ([, size], { theme }) => {
const value = theme.spacing?.[size];
if (value) return { padding: value };
}]// Generator function for multiple CSS values
[/^animate-(.+)$/, function* ([, name], { theme }) {
const animation = theme.animations?.[name];
if (animation) {
yield { animation: animation.value };
if (animation.keyframes) {
yield `@keyframes ${name} { ${animation.keyframes} }`;
}
}
}]// Async rule with external data
[/^icon-(.+)$/, async ([, name]) => {
const iconData = await fetchIcon(name);
return {
'background-image': `url(${iconData.url})`,
'background-size': 'contain',
'background-repeat': 'no-repeat'
};
}]const responsiveVariant = (matcher, { theme }) => {
const match = matcher.match(/^(\w+):(.+)$/);
if (!match) return;
const [, breakpoint, rest] = match;
const size = theme.breakpoints?.[breakpoint];
if (size) {
return {
matcher: rest,
parent: `@media (min-width: ${size})`
};
}
};const containerVariant = (matcher) => {
const match = matcher.match(/^container-(\w+):(.+)$/);
if (!match) return;
const [, size, rest] = match;
return {
matcher: rest,
parent: `@container (min-width: ${size})`
};
};{
name: 'important',
match: (matcher) => {
if (matcher.endsWith('!')) {
return {
matcher: matcher.slice(0, -1),
body: (body) => body.map(([prop, value]) => [prop, `${value} !important`])
};
}
},
multiPass: true
}Static rules are automatically indexed in a hash map for O(1) lookup performance:
// These rules are indexed for fast lookup
['flex', { display: 'flex' }],
['block', { display: 'block' }],
['hidden', { display: 'none' }]Dynamic rules are processed in definition order, so place more specific patterns first:
[
// More specific patterns first
[/^bg-(red|blue|green)-(\d{3})$/, handler1],
[/^bg-(\w+)-(\d+)$/, handler2],
[/^bg-(.+)$/, handler3] // Most general last
]The generator automatically caches rule matching results to avoid recomputation for repeated utility classes.
Install with Tessl CLI
npx tessl i tessl/npm-unocss--core