vanilla-extract
Stylesheet files
Inside .css.ts / .css.js — the only place vanilla-extract allows its APIs — Devup UI evaluates the module and replaces every call with its generated output. The whole surface works there: style, globalStyle, keyframes, styleVariants, fontFace, globalFontFace, createVar, fallbackVar, createTheme, createGlobalTheme, createThemeContract, createGlobalThemeContract, assignVars, layer, globalLayer, createContainer. Generated names — custom properties, containers, layers, theme classes, font families — are unique to each stylesheet, and layers keep the order they are declared in.
1// theme.css.ts 2import { createTheme, createThemeContract, style } from '@vanilla-extract/css' 3 4export const vars = createThemeContract({ colors: { bg: null }, space: null }) 5export const light = createTheme(vars, { 6 colors: { bg: 'white' }, 7 space: '8px', 8}) 9export const box = style({ background: vars.colors.bg }) 10export const base = style({ padding: 8 })1// theme.css.ts 2import { createTheme, createThemeContract, style } from '@vanilla-extract/css' 3 4export const vars = createThemeContract({ colors: { bg: null }, space: null }) 5export const light = createTheme(vars, { 6 colors: { bg: 'white' }, 7 space: '8px', 8}) 9export const box = style({ background: vars.colors.bg }) 10export const base = style({ padding: 8 })
Numbers keep vanilla-extract's meaning, here and in ordinary modules: padding: 8 is 8px, and unitless properties such as lineHeight or zIndex stay as written. As in vanilla-extract, a number for a time gets px too (transitionDuration: 300 is 300px, which browsers ignore), so write times with their unit: transitionDuration: '300ms'.
A stylesheet can import other stylesheets and ordinary modules — ES modules or CommonJS — resolved like the bundler resolves them (relative paths, tsconfig paths and packages). An imported stylesheet is extracted the way the bundler extracts it, so the class names and custom properties it exports are the ones its own CSS uses, and the importer keeps importing it so that CSS is loaded:
1// tokens.ts 2export const PRIMARY = 'blue'1// tokens.ts 2export const PRIMARY = 'blue'
1// button.css.ts 2import { style } from '@vanilla-extract/css' 3 4import { base, vars } from './theme.css' 5import { PRIMARY } from './tokens' 6 7export const button = style([base, { color: PRIMARY, margin: vars.space }])1// button.css.ts 2import { style } from '@vanilla-extract/css' 3 4import { base, vars } from './theme.css' 5import { PRIMARY } from './tokens' 6 7export const button = style([base, { color: PRIMARY, margin: vars.space }])
A change to an imported file rebuilds the stylesheets importing it. Import cycles behave as in ES modules: they work as long as neither side reads the other before it has been evaluated, and reading too early is a build error naming the binding and the module.
A stylesheet that throws while it is evaluated — an import that does not resolve, a property read on undefined — is a build error carrying that exception, rather than output that still calls style() at runtime.
Ordinary modules
Named imports of style, globalStyle and keyframes also resolve in .ts /
.tsx, mapped onto their Devup UI counterparts. Literal rule objects compile
there without evaluating the whole module:
1import { globalStyle, style } from '@vanilla-extract/css' 2 3export const a = style({ color: 'red' }) 4globalStyle('body', { margin: 0 })1import { globalStyle, style } from '@vanilla-extract/css' 2 3export const a = style({ color: 'red' }) 4globalStyle('body', { margin: 0 })
1import "@devup-ui/react/devup-ui.css"; 2export const a = "a"; 3;1import "@devup-ui/react/devup-ui.css"; 2export const a = "a"; 3;
With an empty theme, the same probe emits:
1/*! devup-ui v1.0.82, | Apache License 2.0 | https://devup-ui.com */@layer b;@layer b{body{margin:0}}.a{color:red}1/*! devup-ui v1.0.82, | Apache License 2.0 | https://devup-ui.com */@layer b;@layer b{body{margin:0}}.a{color:red}
Generated names depend on the class prefix and extraction state. The vanilla-extract API import is removed, but the generated CSS import remains.
globalStyle(selector, rules) keeps vanilla-extract's two-argument shape; the extractor folds the selector back into the object globalCss takes.
When the build knows the composed styles, style([base, { ... }]) keeps
non-conflicting declarations and lets the later part win per property,
selector, breakpoint, and layer, as described in
Supported Syntax & Limitations. For example:
1import { style } from '@vanilla-extract/css' 2 3const base = style({ color: 'red', padding: 8 }) 4export const button = style([base, { color: 'blue' }])1import { style } from '@vanilla-extract/css' 2 3const base = style({ color: 'red', padding: 8 }) 4export const button = style([base, { color: 'blue' }])
Extracted code:
1import "@devup-ui/react/devup-ui.css"; 2const base = "a b"; 3export const button = "b c";1import "@devup-ui/react/devup-ui.css"; 2const base = "a b"; 3export const button = "b c";
Generated CSS:
1/*! devup-ui v1.0.82, | Apache License 2.0 | https://devup-ui.com */.c{color:blue}.a{color:red}.b{padding:8px}1/*! devup-ui v1.0.82, | Apache License 2.0 | https://devup-ui.com */.c{color:blue}.a{color:red}.b{padding:8px}
button does not include the conflicting red class. Unknown external classes
still follow the CSS cascade; class-name string order is not an override rule.
Ordinary-module extraction does not execute a vanilla-extract theme contract
to discover its generated custom properties. Keep contracts and their consuming
styles together in .css.ts / .css.js. For example, the stylesheet example
at the top of this page is not valid if saved as an ordinary theme.ts module:
1// theme.ts (not a stylesheet file) 2import { createTheme, createThemeContract, style } from '@vanilla-extract/css' 3 4const vars = createThemeContract({ colors: { bg: null } }) 5export const light = createTheme(vars, { colors: { bg: 'white' } }) 6export const box = style({ background: vars.colors.bg })1// theme.ts (not a stylesheet file) 2import { createTheme, createThemeContract, style } from '@vanilla-extract/css' 3 4const vars = createThemeContract({ colors: { bg: null } }) 5export const light = createTheme(vars, { colors: { bg: 'white' } }) 6export const box = style({ background: vars.colors.bg })
The probe locates the call at line 6, column 20 and reports this message:
1`css()` cannot use `vars.colors.bg` at build time: its values must be literals, theme tokens or constants, or be computed from them1`css()` cannot use `vars.colors.bg` at build time: its values must be literals, theme tokens or constants, or be computed from them
The two-file stylesheet example above likewise does not promise that the same
imports work in an ordinary .ts module:
1// button.ts (not a stylesheet file) 2import { style } from '@vanilla-extract/css' 3 4import { base, vars } from './theme.css' 5import { PRIMARY } from './tokens' 6 7export const button = style([base, { color: PRIMARY, margin: vars.space }])1// button.ts (not a stylesheet file) 2import { style } from '@vanilla-extract/css' 3 4import { base, vars } from './theme.css' 5import { PRIMARY } from './tokens' 6 7export const button = style([base, { color: PRIMARY, margin: vars.space }])
Even when theme.css.ts and tokens.ts resolve, the probe locates the
theme-variable read at line 7, column 23 and reports:
1`css()` cannot use `vars.space` at build time: its values must be literals, theme tokens or constants, or be computed from them1`css()` cannot use `vars.space` at build time: its values must be literals, theme tokens or constants, or be computed from them
Move that consuming module to button.css.ts and export the styles and theme
values from valid stylesheet modules. Ordinary modules can read resolvable
literal constants, but that does not make a generated theme object an ordinary
literal constant. A build-time-known composition must not be confused with a
theme-variable read the ordinary-module path cannot resolve.
The remaining APIs stay imported from @vanilla-extract/css outside a
stylesheet file. For example, createVar() and styleVariants() remain calls
to that package even when a style() call in the same module is transformed.
They are not evaluated by the ordinary-module extraction path.
Moving those calls into a .css.ts file — where vanilla-extract wants them anyway — removes the dependency.
Namespace imports
A namespace stands for many named exports whose Devup UI counterparts are renamed (style → css), which a namespace access cannot express, so it is left alone:
Use named imports to get the rewrite.