Skip to content

Custom Rules

The plugin can be extended through rules and defineRule.

ts
import { defineConfig } from 'vite'
import { defineRule, isColorValue, isSizeValue, utilsJIT } from '@vueland/utils-jit'

export default defineConfig({
  plugins: [
    utilsJIT({
      rules: [
        defineRule({
          name: 'surface',
          matcher: /^surface-\[(.+)\]$/,
          validate: isColorValue,
          declaration: (value) => ({
            backgroundColor: value,
          }),
          important: false,
        }),

        defineRule({
          name: 'size',
          matcher: /^size-\[(.+)\]$/,
          validate: isSizeValue,
          declaration: (value) => ({
            width: value,
            height: value,
          }),
        }),

        defineRule({
          name: 'flex-center',
          matcher: /^flex-center$/,
          declaration: () => ({
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
          }),
        }),

        defineRule({
          name: 'grid-cols',
          matcher: /^grid-cols-(\d+)$/,
          validate: (value) => Number(value) > 0,
          declaration: (value) => ({
            display: 'grid',
            gridTemplateColumns: `repeat(${value}, minmax(0, 1fr))`,
          }),
        }),
      ],
    }),
  ],
})

Use the generated utilities anywhere your framework accepts class strings:

html
<div class="surface-[#fff] size-[40px] hover:size-[48px] flex-center grid-cols-3">
  Custom utilities
</div>

Generated CSS:

css
.surface-\[\#fff\] {
  background-color: #fff;
}
.size-\[40px\] {
  width: 40px !important;
  height: 40px !important;
}
.hover\:size-\[48px\]:hover {
  width: 48px !important;
  height: 48px !important;
}
.flex-center {
  display: flex !important;
  justify-content: center !important;
  align-items: center !important;
}
.grid-cols-3 {
  display: grid !important;
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
}

defineRule API

ts
defineRule({
  name: 'rule-name',
  matcher: /^rule-name-(.+)$/,
  validate: (value) => true,
  declaration: (value) => ({
    cssProperty: value,
  }),
  important: true,
})
Field
Type
Description
name
string
Rule name used for readability and debugging.
matcher
RegExp
Matcher for the utility part without variants.
validate
(value: string, match: RegExpMatchArray) => boolean
Validates the resolved value.
declaration
(value: string, match: RegExpMatchArray) => Record<string, string | number> | string[]
Generates CSS declarations.
important
boolean
Adds !important to object-based declarations. Defaults to true.

matcher receives only the utility part without variants. If the matcher has a capture group, value is the first captured group. If there is no capture group, value is the utility itself. Before validate and declaration run, the resolved value is normalized and checked by the internal CSS value safety guard.

For this class:

html
<div class="hover:surface-[#fff]"></div>

matcher should match:

txt
surface-[#fff]

For a static class, match the utility directly:

ts
defineRule({
  name: 'flex-center',
  matcher: /^flex-center$/,
  declaration: () => ({
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
  }),
})

For parameterized classes without [], use capture groups:

ts
defineRule({
  name: 'grid-cols',
  matcher: /^grid-cols-(\d+)$/,
  validate: (value) => Number(value) > 0,
  declaration: (value) => ({
    gridTemplateColumns: `repeat(${value}, minmax(0, 1fr))`,
  }),
})

Declarations

declaration usually returns an object with CSS properties:

ts
defineRule({
  name: 'bg',
  matcher: /^bg-\[(.+)\]$/,
  validate: isColorValue,
  declaration: (value) => ({
    backgroundColor: value,
  }),
})

CSS properties written in camelCase are automatically converted to kebab-case:

ts
{
  backgroundColor: '#fff',
  borderTopLeftRadius: '8px',
}

Result:

css
background-color: #fff !important;
border-top-left-radius: 8px !important;

CSS variables are preserved:

ts
defineRule({
  name: 'token',
  matcher: /^token-\[(.+)\]$/,
  declaration: (value) => ({
    '--vl-token': value,
  }),
  important: false,
})

Result:

css
--vl-token: #fff;

If declaration returns string[], the strings are treated as final CSS declarations. In this case, !important is not added automatically.

ts
defineRule({
  name: 'raw',
  matcher: /^raw-\[(.+)\]$/,
  declaration: (value) => [`--raw-value: ${value};`],
})

Stricter validation

For custom rules, it is better to explicitly restrict the allowed value format:

ts
import { defineRule } from '@vueland/utils-jit'

const gridColsRule = defineRule({
  name: 'grid-cols',
  matcher: /^grid-cols-(\d+)$/,
  validate: (value) => /^\d+$/.test(value),
  declaration: (value) => ({
    gridTemplateColumns: `repeat(${value}, minmax(0, 1fr))`,
  }),
})

Usage:

html
<div class="grid-cols-3"></div>

Result:

css
.grid-cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
}

Validators

The package exports validators that can be used in custom rules:

ts
import {
  isColorValue,
  isMarginValue,
  isOpacityValue,
  isPaddingValue,
  isPositionValue,
  isRadiusValue,
  isSizeValue,
  isZIndexValue,
} from '@vueland/utils-jit'

Example:

ts
defineRule({
  name: 'text',
  matcher: /^text-\[(.+)\]$/,
  validate: isColorValue,
  declaration: (value) => ({
    color: value,
  }),
})