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="..." and class="..." - static strings
  • className={clsx(...)} - utility function calls
  • className={cond ? "a" : "b"} - ternary and logical expressions
  • classList={{active: cond}} - Solid's classList
  • usage tracking: variables in className, class, and classList are 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-jsx
  • additional_classes - classes to always include (for dynamic patterns that can't be statically extracted)
  • exclude_classes - classes to exclude from output
  • class_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_definitions uses only your own
  • class_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 returns boolean, e.g. (id) => !id.includes('/fixtures/')
  • on_error - 'log' or 'throw'; defaults to 'throw' in CI (CI set to anything but empty, false, or 0), 'log' otherwise
  • on_warning - 'log', 'throw', or 'ignore'; defaults to 'log'
  • cache_dir - cache location; defaults to .fuz/cache/css
  • cache_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 parsers
  • prescan - dev-only scan of sources at server startup; true (default) scans src under the Vite root, false disables, or pass an array of directories
  • base_css - customize or disable base styles; set to null for 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 to null for utility-only mode, or provide an array to replace the defaults or a callback to modify them
  • theme - a theme to bake into the generated CSS, overlaid onto variables last-wins by name
  • additional_elements - elements to always include styles for (for runtime-created elements), or 'all' to include all base styles
  • additional_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:

frameworkdetectionnotes
plain HTMLfullstatic class="..." attributes, script variables
Sveltefullall patterns including class: directives and array/object syntax
React / JSXfullwith acorn-jsx plugin - className
Preactfullwith acorn-jsx plugin - class
Solidfullwith acorn-jsx plugin - class, classList
Vue JSXfullwith acorn-jsx plugin - class
Vue SFC, Angular, etc.nonetemplate 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}` extracts color_a_50, but `color_${hue}_50` cannot be extracted; use @fuz-classes or additional_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