Configuration
Configure zudo-wind generation, tokens, breakpoints, source candidates, dark mode, and resets.
Configure the workspace
This page configures the zudo-wind engine that your project runs.
Put engine settings under the top-level wind key. Configure the reset, tokens, breakpoints, and source roots your classes need:
import { defineConfig } from "zfb/config";
export default defineConfig({
wind: {
spec: 1,
reset: "minimal-v1",
strict: true,
tokens: {
spacingUnit: "0.25rem",
colors: { panel: "var(--app-panel)" },
fontSizes: { sm: { size: "0.875rem", lineHeight: "1.25rem" } }
},
breakpoints: { sm: { minWidthPx: 640 } },
dark: { attribute: "data-theme", value: "dark" },
sources: {
exclude: ["src/**/__tests__/**"],
packageRoots: ["./packages/ui"]
},
safelist: { app: ["sm:hover:bg-panel"] },
authoredClasses: { "legacy-panel": true }
}
});Define custom properties in authored CSS when tokens reference them. Then inspect the same candidate and source plan the build will use:
zfb wind audit --plan build --fail-on warning
zfb wind explain --config zfb.config.ts sm:hover:bg-panel
zfb css --input src/styles.css --output dist/app.css --project-root .Use a safelist for complete app candidates that source discovery cannot find. Publish package-owned candidates as a manifest. Reserve a complete spelling in authoredClasses when CSS owns a name that overlaps a utility root.
<article class="wind-demo-card bg-panel p-4 sm:p-8"><h2>Workspace panel</h2><p>A token and responsive spacing compiled by zudo-wind.</p></article>@layer zw-reset, zw-tokens, zfb-hi, base, components;
@layer zw-tokens {
:root {
--zw-color-panel: #eaf2ff;
--zw-spacing-unit: .25rem;
}
}
.wind-demo-card {
max-width: 24rem;
border: 1px solid #b6c4d2;
border-radius: .5rem;
color: #172b4d;
}
.wind-demo-card h2 {
margin: 0 0 .5rem;
font-size: 1.1rem;
}
.wind-demo-card p {
margin: 0;
}
.p-4 {
padding-top: 1rem;
padding-right: 1rem;
padding-bottom: 1rem;
padding-left: 1rem;
}
.bg-panel {
background-color: var(--zw-color-panel);
}
@media (min-width: 640px) {
.sm\:p-8 {
padding-top: 2rem;
padding-right: 2rem;
padding-bottom: 2rem;
padding-left: 2rem;
}
}Configuration for this example
{
"wind": {
"spec": 1,
"reset": "none",
"strict": true,
"tokens": {
"spacingUnit": "0.25rem",
"colors": {
"panel": "#eaf2ff"
}
},
"breakpoints": {
"sm": {
"minWidthPx": 640
}
},
"defaultTransitionTimingFunction": "ease",
"authoredClasses": {
"wind-demo-card": true
},
"safelist": {
"docs-preview": [
"bg-panel",
"p-4",
"sm:p-8"
]
}
}
}Authored demonstration CSS
.wind-demo-card { max-width: 24rem; border: 1px solid #b6c4d2; border-radius: 0.5rem; color: #172b4d; }
.wind-demo-card h2 { margin: 0 0 0.5rem; font-size: 1.1rem; }
.wind-demo-card p { margin: 0; }The sample contains compiled zudo-wind CSS with an explicit spacing unit, breakpoint, token, and reset. Its authored card styles supply context; the displayed utility classes come from the compiler.
Technical reference
The wind key
The root configuration accepts wind in both zfb.config.ts and zfb.config.json. After the cutover, an absent key has the same behavior as an empty object: generation is enabled with reset none, empty token maps, and sources supplied by zfb's source planner.
import { defineConfig } from "zfb/config";
export default defineConfig({
wind: {
spec: 1,
reset: "none",
defaultTransitionTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
tokens: {
spacingUnit: "0.25rem",
colors: { panel: "var(--app-panel)" }
},
breakpoints: {
sm: { minWidthPx: 640 }
},
dark: { attribute: "data-theme", value: "dark" },
safelist: {
app: ["sm:hover:bg-panel"]
},
authoredClasses: {
prose: true
},
manifests: {
widgets: { path: "@example/widgets/wind.json" }
}
}
});A host token value that differs from a preset binding replaces it silently in build. wind explain identifies the changed binding via optional tokenResolutions[*].hostOverride; wind audit reports ZW015 as audit-only information. Equal values do not produce an override record.
Manifest path resolution
Manifest paths are resolved from a base directory. A bare package specifier such as @example/ uses Node resolution and the producer package's exports; this is the recommended spelling and works with pnpm's package layout. Export the manifest from the producer package:
{
"name": "@example/widgets",
"exports": {
"./wind.json": "./wind.json"
}
}Paths beginning with . or . resolve from the project root, meaning the directory that contains zfb.config.ts or zfb.config.json, regardless of the current working directory. When zfb css or zfb wind loads a file selected with --config, that file's directory takes this role (see Explicit config files). When a manifest declaration comes from a preset built with definePreset(), zfb records the preset package as its source, so both relative paths and bare package specifiers resolve from that preset package's directory instead.
Source exclusions and package roots
sources adjusts the source plan. These declarations are optional and default to empty, so an unset sources keeps the default plan.
wind: {
sources: {
rootPackage: false,
roots: ["src", "tests"],
exclude: ["src/**/__tests__/**", "worker"],
classHelpers: ["uiClasses"],
ignoreAttributes: ["css", "displayJs"],
packageRoots: ["@example/widgets", "./packages/ui"]
}
}sources.rootPackage controls the implicit Wind scan of a workspace root package whenpnpm-workspace.yaml explicitly claims .. It defaults to true; set it to false to skip only that source, or to true to keep it explicit. Project roots, imported sources, declaredsources.roots and sources.packageRoots, manifests, and safelists remain independent inputs. When presets set this scalar, the first preset that supplies it wins; an explicit project value overrides presets, and a project omission inherits the preset value. Without a preset value, omission means true.
Each exclude entry is a glob matched against paths relative to its declaring root: the project root (the --config file's directory when one is selected), or the preset package for a declaration from definePreset(). A pattern excludes a path when it matches the path or one of its parent directories, so worker or worker/ excludes everything under worker/. * does not cross /; use ** for any depth. Exclusions apply to every source root, win over explicit --source files, and only remove files under their own declaring root. Negated (!), absolute, and .. patterns are rejected. Test files are not excluded unless a pattern names them.
sources.roots lists directories strictly below the project or preset that declares them; it cannot name the declaring root or escape above it. These roots follow gitignore rules, standard skipped directories, and wind.sources.exclude. Ordinary .ts and .test.ts files are candidates; declaration .d.ts files are always excluded, and audit coverage names this as excluded **/. packageRoots is a separate first-party package channel with its own traversal rules.
Each packageRoots entry is either a . or . path from the declaring root, or an installed package name with an optional subpath such as @example/, found through the node_modules directories above the declaring root. A package root must be an existing directory that is neither the project root nor one of its ancestors, and it is scanned with its own dist and node_modules, ignoring gitignore rules. Output, --output, and scratch directories stay excluded inside it.
sources.classHelpers explicitly lists local helper identifiers in addition to built-in class helpers. Preset and project lists merge in deterministic order with duplicates removed. Only supported string/template, array, and object arguments are treated as class lists; naming a helper such as cva or twMerge does not infer its unrelated semantics. sources.ignoreAttributes adds names to the built-in ignored props href, src, content, name, rel, and type; preset and project declarations deduplicate. Names accept Unicode identifier characters, optionally followed by - or : segments. class and className are rejected. For example, opt in css, html, js, displayJs, or title when those props carry nonclass content. The complete ignored prop value is excluded, including nested markup, interpolation, and class-helper calls. Actual class attributes and diagnostics outside those props remain visible. No query-selector or script-text heuristic is added.
The same object is valid as the value of the wind property in JSON:
{
"wind": {
"spec": 1,
"reset": "none",
"tokens": {
"spacingUnit": "0.25rem",
"colors": {
"panel": "var(--app-panel)"
},
"fontSizes": {
"sm": {
"size": "0.875rem",
"lineHeight": "1.25rem"
}
}
},
"breakpoints": {
"sm": {
"minWidthPx": 640
}
}
}
}wind: false turns off wind generation, token validation, manifest loading, and candidate scanning in build, dev, and the standalone CSS command. Authored global CSS, CSS Modules, and highlight processing still run. Checks for leftover directives in stylesheets still run even when wind is false. A non-none reset can emit CSS without utility candidates; an empty enabled configuration with no resolved rules emits no wind stylesheet.
Fields
| Field | Meaning |
|---|---|
spec | Optional language version. The only accepted value is 1; the default is 1. |
reset | One of none, minimal-v1, or owned-v1. The default is none. |
defaultTransitionTimingFunction | One CSS timing function or keyword used by transition utilities. It defaults to ease; an explicit ease-* utility takes precedence in the same variant bucket. |
tokens | Project supplied values. All maps default to empty; font-size entries use { size, lineHeight? }; see Font sizes and Tokens for names, value validation, and emitted variables. |
breakpoints | Names mapped to objects containing a positive integer minWidthPx. Equal widths are a configuration error. |
dark | Either false (the default) or an object with required attribute and value strings. |
safelist | Owner IDs mapped to arrays of complete candidates. Each owner supplies its full set. |
authoredClasses | Complete candidate spellings mapped to true; exact matches stay authored and suppress utility validation, diagnostics, and generated CSS. Include variants when reserving a variant-bearing candidate. |
strict | false by default. When true, an unsupported foreign utility (ZW014) at a class position is a build error instead of a warning. Lower-confidence literals are never promoted. |
manifests | Producer IDs mapped to a nonempty path for a candidate manifest. |
sources | Optional roots, exclude, packageRoots, classHelpers, and ignoreAttributes declarations, resolved from each project or preset owner. |
utilities | Optional placement: "after-authored" (the default) or "before-authored". It sets whether unlayered utility rules follow or precede authored global CSS; see Utility placement. |
The dark attribute must be a lowercase HTML attribute identifier. Its value must be nonempty and contain no control characters. Owner and producer IDs use nonempty ASCII letters, digits, dots, underscores, slashes, or hyphens, and start with a letter or digit. Authored class keys must be complete, nonempty tokens without whitespace or control characters.
defaultTransitionTimingFunction takes a CSS timing function value, not a name from tokens.easings. Configure tokens.easings for explicit ease-* utilities; those utilities sort after transition defaults and override them in the same variant bucket. The configured value is validated as one timing function or keyword. transition-none emits no default duration or timing function.
Strict validation and merging
Every nested config object has a closed set of field names. Unknown fields, nulls, wrong value types, unsupported versions or reset names, duplicate breakpoint widths, and invalid token data fail validation. A partial object is allowed because omitted optional objects receive their documented empty defaults.
When preset and user configuration are merged, objects merge recursively with user values taking precedence. Distinct safelist owners and manifest producers remain distinct. Every sources declaration is kept with its own declaring root: preset entries come first in preset order, followed by the user's, and a user declaration does not replace a preset's. For the same safelist owner, the user's array replaces the preset array; arrays are not concatenated. Authored class maps merge by key with user values taking precedence. There is no per-entry deletion marker in v1, and setting wind to false disables the whole system.
A leftover top-level tailwind key is a hard error with a named migration message. Follow the v3 migration guide to update it.