Skip to content

Configuration

utilsJIT accepts an options object.

ts
import { defineConfig } from 'vite'
import { utilsJIT } from '@vueland/utils-jit'

export default defineConfig({
  plugins: [
    utilsJIT({
      include: [/\.(vue|js|ts|jsx|tsx|html|svelte|astro)$/],
      exclude: [/src\/fixtures/],
      emitFile: false,
      outFile: 'src/.generated/utils-jit.css',
      breakpoints: {
        xs: 0,
        sm: 600,
        md: 960,
        lg: 1280,
        xl: 1920,
        xxl: 2560,
      },
      debug: false,
    }),
  ],
})

Add your framework plugin (@vitejs/plugin-vue, @vitejs/plugin-react, etc.) in the same plugins array when your app needs one.

Options

Option
Type
Default
Description
include
Array<string | RegExp>
[/\.(vue|js|ts|jsx|tsx|html|svelte|astro)$/]
Files that should be scanned.
exclude
Array<string | RegExp>
Service directories
Files and directories that should be ignored.
emitFile
boolean
false
Also write the CSS to a file (for debugging). By default the CSS is served only as a virtual module.
outFile
string
src/.generated/utils-jit.css
Path to the debug file relative to the Vite root. Only used when emitFile: true.
breakpoints
Record<string, number>
{ xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 }
Responsive breakpoints. When passed, they are merged with the defaults (values are overridden per key, new keys are added as class prefixes).
attrs
AttrRule[]
[]
Custom attributes/props whose literal values generate arbitrary utility candidates. See Custom attrs.
rules
UtilityRule[]
[]
Custom utility rules.
variants
VariantMap
{}
Custom variants that are added to the built-in variants.
banner
string
/* @vueland/utils-jit: generated utilities */
Banner at the top of the generated CSS.
emitEmptyFile
boolean
true
Serve a placeholder comment when no utilities are found (otherwise empty CSS).
debug
boolean
false
Prints diagnostic messages.

emitFile

By default the CSS is served as a virtual module, virtual:utils-jit.css, and is never written to disk — consumers import the module:

ts
import 'virtual:utils-jit.css'

If you want to inspect the result as a file (for debugging, diffing, external tooling), enable emitFile — the CSS is additionally written to outFile. This does not change delivery: you still import the virtual module.

ts
utilsJIT({
  emitFile: true,
})

outFile

Path to the debug file relative to the Vite project root. Only used when emitFile: true — in the default mode no file is written.

ts
utilsJIT({
  emitFile: true,
  outFile: 'src/styles/generated/utils.css',
})

You still import the virtual module, not this file:

ts
import 'virtual:utils-jit.css'

include

A list of patterns for files that should be scanned.

Default:

ts
;[/\.(vue|js|ts|jsx|tsx|html|svelte|astro)$/]

Example:

ts
utilsJIT({
  include: [/\.(vue|ts)$/],
})

Vue, React, Preact, Solid, Svelte, Astro, HTML, JS, and TS files are included by default. For other file types, include their extensions explicitly:

ts
utilsJIT({
  include: [/\.(vue|js|ts|jsx|tsx|html|svelte|astro|mdx)$/],
})

exclude

A list of patterns for files and directories that should be excluded from the initial scan, transform, and HMR.

By default, the following directories are excluded:

txt
node_modules
.git
dist
build
coverage
.output
.nuxt
.turbo
.generated
storybook-static
playwright-report

Example:

ts
utilsJIT({
  exclude: [/src\/fixtures/, /src\/legacy/, 'storybook-static'],
})

breakpoints

An object of responsive breakpoints. The key is used as a class prefix, the value is the min-width in pixels. When provided, it is merged with the built-in defaults (xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560): the passed keys override values, the remaining default names are kept.

ts
utilsJIT({
  breakpoints: {
    xs: 0,
    sm: 600,
    md: 960,
    tablet: 1280,
    lg: 1280,
    xl: 1920,
    xxl: 2560,
  },
})

After that you can use the new prefix in JIT classes:

html
<div class="sm:w-[640px] tablet:w-[1024px] lg:w-[1280px]"></div>

Why merge, not replace

In standalone usage, merging lets you override one value without losing the built-in responsive prefixes. In a Vueland app, the same names (xs/sm/md/lg/xl/xxl) are also a shared contract: @vueland/ui generates predefined .md\:pa-4, .lg\:d-flex classes and built-in responsive CRow/CCol props from them, and useBreakpoints exposes them at runtime. So you can override values and add new names (e.g. tablet: 1280), but the default names remain available.

Vueland UI: custom breakpoint names in grid components

Custom breakpoint names are compiled into the grid CSS, but Vue components do not receive new props for them. For example, breakpoints: { tablet: 1280 } generates tablet grid classes, but it does not make <c-col tablet="6"> or <c-row align-tablet="center"> valid props.

Use regular class bindings for custom names:

vue
<template>
  <c-row class="tablet:justify-center">
    <c-col cols="12" class="tablet-6">Half width from tablet</c-col>
    <c-col cols="12" class="tablet-4 tablet:offset-1">One third from tablet</c-col>
  </c-row>
</template>

Default breakpoint props such as sm="6", md="4", align-md="center", and justify-lg="space-between" are still available and use the values configured through utilsJIT.

Key naming constraints

Breakpoint keys can be any string — they become CSS class prefixes and are valid in that context. However, if you use @vueland/ui and want the same breakpoints to apply to predefined SCSS utility classes (sm:d-flex, md:pa-4), the keys must be valid SCSS identifiers: they cannot start with a digit.

Key
JIT classes
SCSS utilities (with @vueland/ui)
sm, md, xxl
'2xl', '3xl'
✗ invalid SCSS identifier

If you only use utils-jit without @vueland/ui SCSS, keys like '2xl' are perfectly fine.

attrs

Use attrs to scan custom component props or attributes whose literal values create utility classes at runtime. Configure each entry with defineAttr; see Custom attrs for examples, validators, and the @vueland/ui color prop behavior.

variants

Custom variants allow you to extend the state and selector syntax.

ts
utilsJIT({
  variants: {
    hocus: {
      kind: 'selector',
      value: '&:hover,&:focus',
    },
    selected: {
      kind: 'attribute',
      value: '[aria-selected="true"]',
    },
    tablet: {
      kind: 'media',
      value: 900,
    },
    dark: {
      kind: 'selector',
      value: '[data-theme="dark"] &',
    },
  },
})

emitEmptyFile

When emitEmptyFile: true (the default) and no utility classes are found, the virtual module serves a placeholder comment (and the same text is written to the debug file when emitFile is on):

css
/* @vueland/utils-jit: no utilities found */

When emitEmptyFile: false, empty CSS is served if no utility classes are found.

ts
utilsJIT({
  emitEmptyFile: false,
})

Working with class strings

The plugin scans static class-like strings in the files matched by include. It handles regular HTML/Vue class, Vue :class, React/Preact className, and string literals used in arrays or objects.

Vue examples:

vue
<template>
  <div class="w-[200px]"></div>
  <div :class="['w-[200px]', active && 'px-[16px]']"></div>
  <div :class="{ 'radius-[12px]': rounded }"></div>
</template>

React example:

tsx
export function Card({ active }: { active: boolean }) {
  return <div className={active ? 'w-[320px] px-[16px]' : 'w-[240px] px-[12px]'}>Content</div>
}

Besides classes, the plugin can scan custom literal attributes configured through attrs. In projects with @vueland/ui, the built-in color prop is scanned automatically.

Runtime values are not evaluated. The class must exist in the source code as a static token.

This will not work:

vue
<script setup lang="ts">
const width = 320
</script>

<template>
  <div :class="`w-[${width}px]`"></div>
</template>

This will work:

vue
<template>
  <div :class="isWide ? 'w-[320px]' : 'w-[240px]'"></div>
</template>

How generation works

When Vite starts, the plugin:

  1. Walks through project files.
  2. Skips service directories such as node_modules, .git, dist, build, .generated, and others.
  3. Scans only files that match include.
  4. Extracts utility tokens.
  5. Validates values.
  6. Serves the final CSS as the virtual:utils-jit.css module (and optionally a debug file when emitFile is on).

During development, the plugin updates CSS incrementally:

  • adds rules for new tokens;
  • removes rules when a token is no longer used anywhere;
  • keeps a rule if the same token is still used in another file;
  • skips re-tokenizing files whose content has not changed;
  • reuses token parsing and CSS rule caches;
  • invalidates the virtual module for hot replacement (HMR) without a full page reload.

Limits and safety

To avoid generating unsafe or invalid CSS, the plugin limits arbitrary values:

  • minimum token length: 5;
  • maximum token length: 180;
  • maximum value length: 160;
  • forbidden characters: ;, {, }, <, >;
  • CSS comments are forbidden inside values;
  • the value must contain at least one letter or digit;
  • only a safe subset of CSS value characters is allowed.

This safety check is applied to the resolved value of built-in rules and custom defineRule rules before a rule-specific validate function or declaration function runs.

The following classes will be ignored:

html
<div class="w-[;]"></div>
<div class="w-[{}]"></div>
<div class="w-[<script>]"></div>
<div class="w-[...........................................]"></div>

Recommendations

Use Utils JIT for precise arbitrary values and project-specific generated utilities. It works best as a focused layer next to your design system: keep repeated semantic decisions in theme tokens, presets, or component variants, and use JIT utilities for values and rules that really need to be generated from source usage.

Good:

vue
<template>
  <c-card class="max-w-[720px] px-[24px] radius-[16px]"> Content </c-card>
</template>

Also good for local custom utilities:

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

If a value or behavior becomes a global design decision, move it into a theme token, preset, or component variant.

Troubleshooting

Utilities are not applied

Check that:

  • utilsJIT() is added to vite.config.ts;
  • the application imports virtual:utils-jit.css in its entry file;
  • the project contains at least one supported utility class;
  • the file with the classes matches include;
  • the file is not ignored by exclude.

If no utility classes are found and emitEmptyFile: true, the virtual module serves this comment:

css
/* @vueland/utils-jit: no utilities found */

If emitEmptyFile: false, empty CSS is served when no classes are found.

If emitFile: true is enabled but the debug file did not appear, check that the outFile path is correct and writable within the project.

The class exists, but CSS is not generated

Check that:

  • the file matches include;
  • the file is not ignored by exclude;
  • the class is written statically and is not generated at runtime;
  • the value passes validation;
  • the utility is supported by built-in rules or added through rules;
  • the variant exists in breakpoints or variants.

A responsive prefix does not work

The prefix must be the name of an existing breakpoint. The default set is xs, sm, md, lg, xl, xxl; to add your own (e.g. tablet), pass it in breakpoints — it is merged with the defaults:

ts
utilsJIT({
  breakpoints: {
    tablet: 900, // added to the default xs/sm/md/lg/xl/xxl
  },
})

If you also use @vueland/ui SCSS utilities, avoid keys that start with a digit ('2xl', '3xl'). See Key naming constraints.

A custom rule does not work

Check that matcher describes the utility part without variants.

For this class:

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

matcher should match:

txt
surface-[#fff]