Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Components & templates
  2. Activating the style system

Activating @craft-ts/style#

The typed style system is not a runtime library you import and call. It is a build step: a Vite plugin evaluates every *.style.ts in Node, deduplicates what they registered, and emits one stylesheet. Without that plugin the vocabulary still typechecks and still compiles — and the page renders with no CSS at all.

This page is the one to follow before the other four.

Install#

bash
npm install @craft-ts/style
npm install --save-dev @craft-ts/dev-tools
# Optional, for visual scenario matrices and attestation:
npm install --save-dev @craft-ts/style-testing

@craft-ts/style carries the vocabulary — tokens, kinds, typed custom properties, axes, sheets, obligations. @craft-ts/dev-tools carries the craft-graph contrast command and the ESLint guard rails. @craft-ts/style-testing carries the optional scenario matrix and the drivers that reach each of its points; it never ships to the browser, so it belongs in devDependencies when visual scenarios or attestation are enabled.

@craft-ts/style declares @craft-ts/core as a peer dependency, and @craft-ts/style-testing declares @craft-ts/style. Keep all installed @craft-ts/* packages on the same release line. The runtime style package and the optional testing package are both sideEffects: false.

Wire the plugin#

ts
import { defineConfig } from 'vite';
import { craftStyle } from '@craft-ts/style/vite';

export default defineConfig({
  plugins: [
    craftStyle({
      // Written on every emission, so the graph never reads a picture of the
      // sheet that is older than the CSS the browser got.
      dumpPath: 'tmp/craft-style-graph.json',
    }),
  ],
});

craftStyle takes six options, all optional:

optiondefaultwhat it decides
suffix'.style.ts'the filename suffix that marks a module as a sheet
ignore['node_modules', 'dist', '.git', '.nx', 'tmp']directory names the walk never descends into
dumpPathnone — no dump is writtenwhere to write the graph dump
aliasnonemodule aliases for the Node evaluation of the sheets
resettrueship the craft-ts reset — see Global foundation
basetrueship colour scheme, focus ring, reduced motion, selection

alias exists because the sheets are evaluated by a real bundler in a separate pass, before your app's own resolution applies. In a published project, Node resolution finds @craft-ts/style on its own and you can leave alias out. In this monorepo the demo passes the workspace source paths — see apps/demo/vite.config.ts, which is the working reference for everything on this page.

Then import the emitted sheet once, at the app entry:

ts
import 'virtual:craft-style.css';

If your tsconfig does not already know that id, declare it next to your other ambient types:

ts
declare module 'virtual:craft-style.css';

What the plugin produces#

Two artefacts, from one evaluation.

The CSS. Every when(...) and set(...) the sheets registered, rendered as atomic rules and @property registrations, deduplicated across files, and served under virtual:craft-style.css. This is the whole stylesheet: no class is ever assembled in the browser, so what the browser gets is exactly what the emitter proved.

The dump, when dumpPath is set. A JSON picture of the registry — classes, atoms, and typed variables — written on every emission, so it can never describe a sheet older than the CSS that was served alongside it. The dump is the style half of the dependency graph: it is what style_impact, style_matrix and style_debt read, and what the @craft-ts/dev-tools style queries read.

The plugin re-derives the whole sheet when a *.style.ts changes rather than patching it. Atomic output is small and the emission is one bundle away; an incremental path here would be a second source of truth about what the CSS says.

What breaks without it#

missingsymptom
the pluginno CSS at all — the classes exist as strings, nothing ever wrote their rules
import 'virtual:craft-style.css'same, and less obviously: the plugin runs but nothing pulls its output
dumpPathstyle_matrix and the other graph queries have nothing to read and say so

The MCP server names the fix in its own error message: it points you back at craftStyle({ dumpPath }). If you are reading this because you saw that message, dumpPath is the line you are missing.

Emitting without a Vite server#

@craft-ts/style/vite exports the emitter itself, so a test or a script can get the same two artefacts without standing up a dev server:

ts
import { emitStyles } from '@craft-ts/style/vite';

// The same two artefacts the plugin produces, without a Vite server: `css` is
// what the browser gets, `dump` is what the graph and the MCP tools read.
export const emitOnce = () =>
  emitStyles(['src/app/ui/button.style.ts', 'src/app/ui/foundation.style.ts']);

vite is a peer of that entry point, not a dependency: a project that builds with something else can still call emitStyles without pulling Vite's types into its own program.

Next#

  • Define your design system — palette, axes, theme: where bp, scheme and palette come from.

  • Tokens and typed variables — level 1.

  • Axes and the visual matrix — level 2.

←ComponentsTyped styles→

On this page

  • Install
  • Wire the plugin
  • What the plugin produces
  • What breaks without it
  • Emitting without a Vite server
  • Next

@craft-ts · MIT licensed

GitHubnpm

Search