setup #
The Vite plugin is the recommended setup. It generates one stylesheet holding the theme variables, the base styles for the elements you use, and the utility classes you use, and it updates with HMR as you edit. It works with SvelteKit, Svelte, React, Preact, Solid, and plain HTML/TS/JS. Without a build step, link the package's two plain stylesheets instead.
npm i -D @fuzdev/fuz_css Working setups for each framework live in the repo's examples.
SvelteKit #
// vite.config.ts
import {defineConfig} from 'vite';
import {sveltekit} from '@sveltejs/kit/vite';
import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.ts';
export default defineConfig({
plugins: [sveltekit(), vite_plugin_fuz_css()],
}); Import the virtual module once, from the root layout:
// src/routes/+layout.svelte
import 'virtual:fuz.css';Vite with React, Preact, or Solid #
JSX needs a parser plugin, which you install yourself: acorn-jsx, passed through the acorn_plugins option.
npm i -D @fuzdev/fuz_css acorn-jsx // vite.config.ts
import {defineConfig} from 'vite';
import react from '@vitejs/plugin-react';
import jsx from 'acorn-jsx';
import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.ts';
export default defineConfig({
plugins: [react(), vite_plugin_fuz_css({acorn_plugins: [jsx()]})],
}); Preact is the same with @preact/preset-vite. For Solid, list the fuz_css plugin
before solid(), as the Solid example does:
plugins: [vite_plugin_fuz_css({acorn_plugins: [jsx()]}), solid()], Then import the virtual module in your entry file:
// src/main.tsx
import 'virtual:fuz.css'; Supported JSX patterns:
className="..."andclass="..."- static stringsclassName={clsx(...)}- utility function callsclassName={cond ? "a" : "b"}- ternary and logical expressionsclassList={{active: cond}}- Solid's classList- usage tracking: variables in
className,class, andclassListare tracked back to their definitions, within one file
// variable tracking works in JSX too
const styles = 'box hover:shadow_lg';
const Component = () => <div className={styles} />; The acorn_plugins option accepts any Acorn-compatible plugin, so other syntax
extensions can be supported the same way.
Vite with Svelte or plain HTML #
Svelte and plain HTML/TS/JS need no parser plugin. With the Svelte plugin outside SvelteKit, list the fuz_css plugin first, as the Svelte example does:
// vite.config.ts
import {defineConfig} from 'vite';
import {svelte} from '@sveltejs/vite-plugin-svelte';
import {vite_plugin_fuz_css} from '@fuzdev/fuz_css/vite_plugin_fuz_css.ts';
export default defineConfig({
plugins: [vite_plugin_fuz_css(), svelte()],
}); // src/main.ts
import 'virtual:fuz.css';Without a build step #
The package ships two plain stylesheets, the base style.css and theme.css with every variable, which work in any page, and jsDelivr serves them from npm:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fuzdev/fuz_css@0.67.0/dist/theme.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fuzdev/fuz_css@0.67.0/dist/style.css" /> That's the full semantic styling and every style variable, including the builtin classes like selected and palette_a. Utility classes need the plugin, since
they're generated from your source, and so does baking a theme in; without it, write the variables into your own
CSS. Pin a version in the URL so a release can't change your page.
TypeScript #
Declare the virtual module once, in a dedicated declaration file like src/fuz_css.d.ts (not a SvelteKit app.d.ts, which is a module):
// src/fuz_css.d.ts
declare module 'virtual:fuz.css';Utility-only mode #
The virtual:fuz.css import alone is all most projects need. Projects managing
their own theme or base styles can turn the generated ones off and import the full
stylesheets, so neither layer ships twice:
// vite.config.ts
vite_plugin_fuz_css({
base_css: null,
variables: null,
}),
// entry file - full package CSS, only used utilities
import '@fuzdev/fuz_css/style.css'; // all base styles
import '@fuzdev/fuz_css/theme.css'; // all variables
import 'virtual:fuz.css';Root font size #
Every rem length on the page is measured against that root, including ones that come
from elsewhere: a third-party component sized for a 16px rem renders at 62.5% of what
its author intended. Size your own lengths with the style variables, and check any rem-based CSS
you bring in. A @media query is the exception, since its rem is the
browser's initial font size: the sm: breakpoint's 40rem is 640px.
Coming from Tailwind, whose spacing scale assumes a 16px rem (p-4 is 1rem, 16px): here 16px is 1.6rem, and the spacing tokens like p_lg carry the scale, so pasted Tailwind lengths come out smaller.
/* with fuz_css's root, 1rem is 10px */
.card { padding: 1rem; } /* 10px, not 16px */
.card { padding: var(--space_lg); } /* the spacing scale */Plugin options #
The plugin needs Vite 6 or later. It extracts classes from files as Vite processes them,
including from node_modules dependencies, and in dev it pre-scans your sources
and the root index.html at startup so the first page load is fully styled. List
it after any plugin that rewrites CSS in its transform hook.
acorn_plugins- required for JSX frameworks, e.g.acorn-jsxadditional_classes- classes to always include (for dynamic patterns that can't be statically extracted)exclude_classes- classes to exclude from outputclass_definitions- custom class definitions to merge with defaults; can define new classes or override existing ones (see composite classes). A callback receives the defaults and returns the whole set, so() => my_definitionsuses only your ownclass_interpreters- custom interpreters for dynamic class generation; an array replaces the default interpreters, and a callback receives them and returns the ones to use (most users don't need this)filter_file- custom filter for which files to process. Receives(id: string, root: string)and returnsboolean, e.g.(id) => !id.includes('/fixtures/')on_error-'log'or'throw'; defaults to'throw'in CI (CIset to anything but empty,false, or0),'log'otherwiseon_warning-'log','throw', or'ignore'; defaults to'log'cache_dir- cache location; defaults to.fuz/cache/csscache_salt- a string folded into the cache key; change it when only an acorn plugin's options change, since the key already covers file content, the plugins, and the installed versions of fuz_css and its parsersprescan- dev-only scan of sources at server startup;true(default) scanssrcunder the Vite root,falsedisables, or pass an array of directoriesbase_css- customize or disable base styles; set tonullfor utility-only mode, or provide a string to replace the defaults or a callback to modify them (see what gets included)variables- customize or disable theme variables; set tonullfor utility-only mode, or provide an array to replace the defaults or a callback to modify themtheme- a theme to bake into the generated CSS, overlaid ontovariableslast-wins by nameadditional_elements- elements to always include styles for (for runtime-created elements), or'all'to include all base stylesadditional_variables- variables to always include in theme output, or'all'to include all theme variables
Framework support #
The stylesheets work with any framework and plain HTML. The utility class generator's detection support varies by framework:
| framework | detection | notes |
|---|---|---|
| plain HTML | full | static class="..." attributes, script variables |
| Svelte | full | all patterns including class: directives and array/object syntax |
| React / JSX | full | with acorn-jsx plugin - className |
| Preact | full | with acorn-jsx plugin - class |
| Solid | full | with acorn-jsx plugin - class, classList |
| Vue JSX | full | with acorn-jsx plugin - class |
| Vue SFC, Angular, etc. | none | template syntax not parsed; use clsx/cx/cn in
JS/TS |
The additional_classes option is an escape hatch for classes that can't be statically detected. Angular and Vue's single-file components aren't supported; we could revisit this if there's demand.
Svelte-first #
The extractor parses and analyzes the AST to understand Svelte's class syntax. Supported constructs:
- attributes:
class="...",class={[...]},class={{...}}(identifier and string-literal keys),class:name - expressions: logical (
&&,||,??), ternaries, template literals (complete tokens only -`color_a_50 ${base}`extractscolor_a_50, but`color_${hue}_50`cannot be extracted; use@fuz-classesoradditional_classes) - Svelte 5 runes:
$derived()and$derived.by()for class variables - utility calls:
clsx(),cn(),cx(),classNames()with nested arrays, objects, and utility calls - scripts: both
<script>and<script module>, with naming convention and usage tracking