Skip to content

Custom Rules

The plugin can be extended through rules and defineRule.

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

export default defineConfig({
  plugins: [
    vue(),
    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,
          }),
        }),
      ],
    }),
  ],
})

Now you can use:

vue
<template>
  <div class="surface-[#fff] size-[40px] hover:size-[48px]">
    Custom utilities
  </div>
</template>

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

defineRule API

ts
defineRule({
  name: 'rule-name',
  matcher: /^rule-name-\[(.+)\]$/,
  validate: (value) => true,
  declaration: (value) => ({
    cssProperty: value,
  }),
  important: true,
})
FieldTypeDescription
namestringRule name used for readability and debugging.
matcherRegExpMatcher for the utility part without variants.
validate(value: string) => booleanValidates the value inside [].
declaration(value: string) => Record<string, string | number> | string[]Generates CSS declarations.
importantbooleanAdds !important to object-based declarations. Defaults to true.

matcher receives only the utility part without variants.

For this class:

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

matcher should match:

txt
surface-[#fff]

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-\[(.+)\]$/,
  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,
  }),
})