UnoCSS
Configure the UnoCSS preset, file scanning pipelines, variant groups, and semantic color variable generation.
Moraine provides first-class support for UnoCSS through the presetMoraine() preset exported from moraine/unocss. It maps semantic colors, border radiuses, shadows, animation keyframes, z-index utilities, and state variants directly into UnoCSS.
Installation and config#
In an existing Solid/Vite project, install Moraine and the UnoCSS integration:
Add presetMoraine() alongside your preferred Wind preset (presetWind4 or presetWind3). Wind4 is the default; set wind3: true for Wind3 compatibility. The preset does not detect the installed Wind preset automatically. Explicitly scan application files and Moraine’s published classes:
Solid and Vite integration#
Place unocss.config.ts and vite.config.ts in the application root. Register UnoCSS before Solid’s plugin:
Import the generated CSS once in the application entry, so it is available on every route:
The HTML entry must have a matching mount element and load src/main.tsx:
Use the App from the installation example to check the button’s appearance. No MoraineProvider is required for the preset’s default theme. If you use TypeScript, include vite/client in your compiler types to recognize the virtual CSS import.
Scanning component classes#
Scan the whole moraine/dist directory with the glob above. The preset does not configure scanning paths for your application.
Filesystem scanning reads these classes independently of which modules have been requested through Vite. Keep both the application and package globs, including lazy-loaded route files and .ts files containing class maps. The pipeline.include filter also applies to filesystem extraction in @subf/unocss/vite; UnoCSS’s default filter omits .mjs and .ts. Keep the extensions your app uses when changing this filter because it replaces the default. See UnoCSS’s extraction guide.
Filesystem globs are relative to Vite’s root. The examples assume src/ and node_modules/moraine live under that root. If you set a different Vite root, keep routes outside src/, or use a workspace with Moraine installed at the repository root, adjust the globs to match those locations. Add your Markdown or MDX directories when they contain application classes.
Verification and troubleshooting#
Verify the button from the installation guide on a clean dev-server start, after a restart with Vite’s dependency cache retained, when directly opening a lazy-loaded route, and in a production preview. Check its background, padding, and border radius; a click handler working does not verify CSS generation.
If styles are missing:
- Confirm
virtual:uno.cssis imported in the application entry and UnoCSS runs before the Solid plugin. - Confirm both the Wind preset and
presetMoraine()are enabled. With Wind3, passwind3: true. - Check that the filesystem globs match the installed Moraine distribution and all application source directories, including lazy routes.
- Check that
pipeline.includeallows those file extensions and that custom exclusion filters do not reject the files. - Inspect a missing class in browser DevTools. A missing CSS rule points to extraction or utility configuration; an existing rule with an unresolved
--primaryor another theme variable points to theme configuration. WiththemeDefaults: false, the application must provide its theme variables.
For a suspected dependency-cache issue, stop the server and run your Vite dev command with --force (for a dev script that runs Vite, pnpm run dev --force). Alternatively, remove Vite’s configured cacheDir, which defaults to node_modules/.vite, before restarting. Disable browser caching temporarily while diagnosing cached dependency requests. See Vite’s dependency caching guide.
After correcting the scan configuration, restart again with the cache retained. Clearing the cache is a diagnostic step, not a required part of normal startup. An improvement after --force alone does not establish a Moraine runtime bug. Restart after upgrading Moraine so its published files are scanned again.
Keep the CSS import in the application entry when using lazy routes; do not rely on visiting another route to load styles. Filesystem scanning can discover static classes before the route is loaded, but neither it nor pipeline extraction can infer arbitrary runtime strings such as bg-${color}. Use complete static class maps or an explicit UnoCSS safelist for application-generated classes.
Preset options#
presetMoraine() provides Moraine’s utility tokens, state variants, opaque neutral light and dark colors as RGB values, a translucent backdrop, default shadows, and base values for radius, font size, and spacing. Dark border and input use rgb(35, 35, 35) and rgb(47, 47, 47); their colors stay the same across parent surfaces. The default selectors are :root and .dark. Configure single-value tokens at the top level; put colors, shadows, and colorScheme in named override entries.
Semantic shadow defaults use the Tailwind values inherited by the shadcn/ui neutral theme: surface → sm, overlay → md, input → xs. The preset emits --shadow-surface, --shadow-overlay, and --shadow-input at :root; dark mode inherits these values unless override.dark.shadows supplies replacements. Native size utilities such as shadow-xs retain the CSS engine’s own values and configuration. Set themeDefaults: false to omit default colors and semantic shadows while retaining explicit overrides.
| Option | Default | Behavior |
|---|---|---|
wind3 |
false |
Enables Wind3 theme mappings and semantic color alpha compatibility. Native numeric spacing and sizes remain unchanged. |
themeDefaults |
true |
Emits neutral light/dark colors and color schemes, default shadows, and HTML background/text styles. Set false when your CSS defines the theme. |
override |
omitted | Overrides the built-in neutral light and dark colors or adds named themes. |
override.<name>.colorScheme |
light for the built-in light theme; dark for dark; omitted for custom themes |
Accepts light or dark only. Emits color-scheme on the theme selector; explicit values still apply with themeDefaults: false. |
colorStates |
{ hover: 8, active: 12 } |
Generates missing hover and active colors for configured base colors. Set false to disable. |
radius |
0.625rem |
Base radius written to --radius at :root; override per theme when needed. |
fontSize |
1rem |
Base font size written to --font-size at :root; used by Wind4 typography rules. |
spacing |
0.25rem |
Base spacing unit written to --spacing at :root. |
sidebarWidth |
omitted | Writes an explicit global --sidebar-width at :root; overridable per theme. SidebarFrame supplies its own fallback when unset. |
Theme tokens#
For grouped colors such as primary and muted, strings are shorthand for { base: value }. Expand these colors to override foreground, hover, or active without repeating base. Single-value colors such as control, input, border, ring, and backdrop accept CSS strings only. control overrides field fills independently of boundaries and parent surfaces. Available color names and fields match Moraine’s semantic colors. Colors and shadows belong inside named override entries. shadows supports only surface, overlay, and input. The semantic keys emit --shadow-surface, --shadow-overlay, and --shadow-input; see Backdrop and Semantic Shadows. Single values use radius, fontSize, spacing, and sidebarWidth. Font families are configured in CSS through --font-sans, --font-mono, and --font-serif; see Typography and Spacing. Other custom properties belong in your CSS file.
light and dark default to :root and .dark. Another name defaults to [data-theme="name"]; set selector on any entry to use a complete CSS selector. The preset emits the default light colors first, then dark colors, then custom themes in configuration order. Light and dark overrides merge with their respective defaults. A partial custom theme inherits missing variables through the CSS cascade, including variables used by portaled overlays when they are declared on an ancestor of the portal.
The built-in light and dark themes emit color-scheme: light and color-scheme: dark, respectively, so native browser controls follow the theme. colorScheme has type 'light' | 'dark' and is configured inside override.<name>, rather than at the top level. Its declaration follows the entry’s selector, including custom selectors for light and dark. Custom themes inherit the CSS color scheme unless explicitly configured. themeDefaults: false omits the default color schemes while retaining explicit overrides.
See Moraine-specific Variables for the additional form surface, backdrop, shadow, interaction, typography, sidebar, and animation variables. colorScheme writes the native CSS color-scheme property; it does not create a custom property.
Semantic color alpha modifiers, such as bg-primary/20, use color-mix(in srgb, …) in supported browsers. With wind3: true, Wind3 keeps the unmodified semantic color as its fallback when color-mix() is unavailable.
Wind3 compatibility#
Use presetMoraine({ wind3: true }) with presetWind3(). This enables Wind3’s semantic token mappings and color alpha compatibility. Numeric spacing and size utilities keep Wind3’s native values and explicit theme overrides; changing --spacing does not scale them. Use explicit variable utilities such as p-(--app-padding) for runtime spacing changes.
With Wind3, configure font sizes through UnoCSS’s theme.fontSize; --font-size does not scale typography. Customize named radiuses through theme.borderRadius. With Wind4, local --font-size and --radius values scale the default typography and radius tokens; customize them through theme.text and theme.radius. Explicit UnoCSS theme values take precedence in both modes. Use leading-* to set line height independently of font size.
Font families#
Configure font families through UnoCSS’s theme object, separately from presetMoraine() options. Wind4 uses theme.font; Wind3 uses theme.fontFamily:
For Wind3, use presetWind3() with presetMoraine({ wind3: true }) and put the same family entries under theme.fontFamily. These explicit theme values take precedence over Moraine’s variable references. To keep runtime font switching, use values such as sans: 'var(--font-sans)' and define the corresponding CSS variables in your stylesheet. Load any custom font files separately.
Hover and active colors#
For grouped semantic colors, when color-mix(in oklch, …) is supported, missing states mix the color toward its foreground in the scope where the utility is used. background uses the global foreground; other grouped colors use their own foreground with the global foreground as a fallback. An explicit state variable always wins. An explicit state can be a CSS color, a mixing percentage, or a callback:
The callback receives its selector, color name, state, configured base or CSS variable reference, foreground value or reference, and current adjustment. Percentages must be finite values from 0 to 100. colorStates: false stops automatic generation; explicit states still emit. Generated utilities retain their CSS fallbacks: active → hover → base.
Without color-mix(in oklch, …), Moraine’s state expression falls back to the base color in the utility’s scope unless you provide an explicit state color. This keeps nested CSS themes from inheriting a stale state color from their parent. Numeric state overrides need color-mix(); use a widely supported CSS color string when a distinct state must work in older browsers. Wind4 itself emits color-mix(in srgb, …) for color utilities, so browsers without any color-mix() support need Wind3. When CSS supplies a shadcn/ui palette, extend it with Moraine’s --control variable and disable the built-in palette:
Define your :root and .dark color variables, including an explicit control surface:
themeDefaults: false omits control values unless explicitly configured in override. A shadcn/ui palette alone is no longer sufficient; see the form surface migration. control has no foreground or state family, uses the existing field foreground, and supports explicit utility opacity such as bg-control/30. The preset still emits the default --radius, --font-size, and --spacing values when themeDefaults is false; your CSS can override them. You can add --primary-hover and other state variables there when older browsers need a distinct state color. baseStyles has been removed; themeDefaults is its replacement and also controls the built-in colors.