Configuration
utilsJIT accepts an options object.
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
includeArray<string | RegExp>[/\.(vue|js|ts|jsx|tsx|html|svelte|astro)$/]excludeArray<string | RegExp>emitFilebooleanfalseoutFilestringsrc/.generated/utils-jit.cssemitFile: true.breakpointsRecord<string, number>{ xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, xxl: 2560 }attrsAttrRule[][]rulesUtilityRule[][]variantsVariantMap{}bannerstring/* @vueland/utils-jit: generated utilities */emitEmptyFilebooleantruedebugbooleanfalseemitFile
By default the CSS is served as a virtual module, virtual:utils-jit.css, and is never written to disk — consumers import the module:
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.
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.
utilsJIT({
emitFile: true,
outFile: 'src/styles/generated/utils.css',
})You still import the virtual module, not this file:
import 'virtual:utils-jit.css'include
A list of patterns for files that should be scanned.
Default:
;[/\.(vue|js|ts|jsx|tsx|html|svelte|astro)$/]Example:
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:
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:
node_modules
.git
dist
build
coverage
.output
.nuxt
.turbo
.generated
storybook-static
playwright-reportExample:
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.
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:
<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:
<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.
@vueland/ui)sm, md, xxl'2xl', '3xl'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.
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):
/* @vueland/utils-jit: no utilities found */When emitEmptyFile: false, empty CSS is served if no utility classes are found.
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:
<template>
<div class="w-[200px]"></div>
<div :class="['w-[200px]', active && 'px-[16px]']"></div>
<div :class="{ 'radius-[12px]': rounded }"></div>
</template>React example:
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:
<script setup lang="ts">
const width = 320
</script>
<template>
<div :class="`w-[${width}px]`"></div>
</template>This will work:
<template>
<div :class="isWide ? 'w-[320px]' : 'w-[240px]'"></div>
</template>How generation works
When Vite starts, the plugin:
- Walks through project files.
- Skips service directories such as
node_modules,.git,dist,build,.generated, and others. - Scans only files that match
include. - Extracts utility tokens.
- Validates values.
- Serves the final CSS as the
virtual:utils-jit.cssmodule (and optionally a debug file whenemitFileis 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:
<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:
<template>
<c-card class="max-w-[720px] px-[24px] radius-[16px]"> Content </c-card>
</template>Also good for local custom utilities:
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 tovite.config.ts;- the application imports
virtual:utils-jit.cssin 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:
/* @vueland/utils-jit: no utilities found */If emitEmptyFile: false, empty CSS is served when no classes are found.
If
emitFile: trueis enabled but the debug file did not appear, check that theoutFilepath 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
breakpointsorvariants.
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:
utilsJIT({
breakpoints: {
tablet: 900, // added to the default xs/sm/md/lg/xl/xxl
},
})If you also use
@vueland/uiSCSS 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:
<div class="hover:surface-[#fff]"></div>matcher should match:
surface-[#fff]