CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-unocss--core

The core atomic CSS engine for UnoCSS that generates CSS on-demand without any presets or default utilities.

Pending
Overview
Eval results
Files

rules-variants.mddocs/

Rules and Variants

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.

Rule System

Rule Types

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

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

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))`
})]

Rule Context

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:

  • rawSelector: Original utility class name
  • currentSelector: Processed selector after variant matching
  • generator: UnoCSS generator instance
  • theme: Resolved theme object
  • constructCSS: Helper to construct CSS with variant processing

Rule Metadata

interface RuleMeta {
  layer?: string;
  noMerge?: boolean;
  sort?: number;
  autocomplete?: Arrayable<AutoCompleteTemplate>;
  prefix?: string | string[];
  internal?: boolean;
  custom?: Record<string, any>;
}

Rule metadata controls:

  • layer: CSS layer assignment
  • noMerge: Prevent selector merging optimization
  • sort: Fine-tune rule ordering
  • prefix: Required prefixes for rule matching
  • internal: Hide from user code (shortcuts only)

Variant System

Variant Types

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>;
}

Variant Functions

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)'
    };
}

Variant Handlers

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;
}

CSS Value Types

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;

Advanced Rule Examples

Theme Integration

// 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 Functions

// 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 Rules

// 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'
  };
}]

Advanced Variant Examples

Complex Media Queries

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})`
    };
  }
};

Container Queries

const containerVariant = (matcher) => {
  const match = matcher.match(/^container-(\w+):(.+)$/);
  if (!match) return;
  
  const [, size, rest] = match;
  return {
    matcher: rest,
    parent: `@container (min-width: ${size})`
  };
};

Multi-Pass Variants

{
  name: 'important',
  match: (matcher) => {
    if (matcher.endsWith('!')) {
      return {
        matcher: matcher.slice(0, -1),
        body: (body) => body.map(([prop, value]) => [prop, `${value} !important`])
      };
    }
  },
  multiPass: true
}

Performance Optimization

Static Rule Indexing

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 Rule Ordering

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
]

Rule Caching

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

docs

config.md

extraction.md

generator.md

index.md

rules-variants.md

types.md

utilities.md

tile.json