Vext JSCSS
Table of Contents
- When to use JSCSS
- Build your first component style
- How extraction reaches the browser
- Common styling tasks
- CSS variables: build-time declarations and browser changes
- Configuration choices
- Troubleshooting
When to use JSCSS
Vext JSCSS turns a TypeScript object into a generated CSS class at build time. Use it when a component needs named variants, semantic CSS variables, or nested rules while you still want the final browser to load CSS rather than a CSS-in-JS runtime.
Choose the smallest tool that fits the job:
Vext does not compile Sass or SCSS source files. If a team keeps Sass, compile it to CSS before Vext sees it. JSCSS is not a Sass replacement; it is the built-in path for typed, component-level generated CSS.
Build your first component style
In an application that has completed Full-Stack Quick Start, define a named recipe in a *.style.ts file, then call it from React's className. The following example includes the style, component, page, and route; leave the default JSCSS setting enabled.
1. Define the button recipe
Create src/frontend/styles/button.style.ts.
recipe() accepts rule objects in base and variants. style() already returns a class-name string, so do not write base: style({ ... }) or primary: style({ ... }) inside a recipe. Give the recipe a name so generated classes are recognizable when you inspect HTML or CSS.
2. Use the recipe in a React component
Create src/frontend/components/Button.tsx.
button({ intent: "primary" }) returns the base class plus the matching variant class. The default variant means button() also produces a primary button when no selection is supplied.
3. Render it from a page
Create the page and an explicit HTTP route. Relative TypeScript imports in the default NodeNext template use the .js extension; framework aliases retain the template's existing mapping.
/settings should show the danger-styled button. This example demonstrates appearance only; the button has no delete behavior.
How extraction reaches the browser
Run the normal production build:
By default, Vext scans **/*.style.ts, **/*.style.js, and **/*.css.ts under frontend.root (src/frontend by default). During a Node build step it executes matching modules and their dependencies and writes their rules into generated JSCSS CSS. Scanning is not limited to files imported by a page. The generated browser entry references the CSS, and the client asset manifest carries it into the document. Customize the scan with frontend.styles.jscss.files.
You do not import an Emotion or styled-components runtime for this path. The className returned by style() or recipe() is the bridge from React to extracted CSS.
Keep a *.style.ts module declarative: it runs during a Node build step, so do not read window, document, request data, or server-only services at module scope.
Common styling tasks
Make one named class
Use style() when a component only needs one class.
Numbers become pixel values where CSS expects a length. Unitless properties such as opacity, zIndex, and fontWeight stay unitless.
Add hover and media rules
Nested selectors use &; at-rules stay inside the same object.
This replaces the preceding style declaration and reuses that file's style import:
Choose a variant at render time
Use a recipe for a finite set of visual choices. Keep selection names meaningful to the component (intent, size, state) rather than mirroring raw CSS values.
This is a component usage snippet: the application supplies isDestructive from props or state. Recipe selection currently resolves string keys; an unknown choice does not generate a new rule. Do not treat arbitrary runtime strings as declared variants or assume compile-time exhaustiveness over every variant name.
CSS variables: build-time declarations and browser changes
createVar() creates a semantic CSS custom-property reference. setVar() returns an object that can be placed in a JSCSS rule; it does not mutate the browser document by itself.
The example emits an initial declaration on the panel element and a var(--vext-accent, #4f46e5) reference. To change it after hydration, update that element from an event handler or effect. Here element is the panel HTMLElement and accent is imported from the style module:
Do not access the DOM at style-module scope or during SSR rendering. Setting the same variable on the root alone does not override the panel's own declaration. For a global theme, define the variable on the root, let components inherit it, then change it with document.documentElement.style.setProperty. createVar() returns a variable descriptor: use it directly as a JSCSS property value, or use its ref when a string is needed. Do not interpolate the entire object into a CSS string.
Configuration choices
JSCSS is enabled by default. Only change its settings when you have a specific delivery constraint:
See Frontend Configuration for the complete field reference and defaults.
Troubleshooting
Next: compare Styles and Assets for the other supported styling paths, or read Frontend Configuration when you need to tune JSCSS extraction.
Verify the Example
After npm run build succeeds, run npm start -- --port 3000 and open /settings. Check the class in SSR HTML, the corresponding rule in browser CSS, and the danger button's actual color. A class string alone does not prove the CSS was loaded. For variable updates, check the target element's computed style. A file outside the build scan is not turned into extracted CSS merely because style() is called at runtime. Stop the service when finished.