Skip to content

Your First Build

If you ran pnpm create dispersa and chose the TypeScript Starter template, you already have a working project. Let’s explore what you got and run the build.

The scaffold creates this structure:

my-dispersa-project/
├── tokens/
│ ├── base/
│ │ ├── colors.json # Foundational color palette
│ │ ├── spacing.json # Spacing scale
│ │ ├── typography.json # Font families, sizes, weights
│ │ └── effects.json # Shadows and effects
│ ├── semantic/
│ │ ├── colors.json # Semantic aliases referencing base tokens
│ │ └── typography.json # Semantic typography aliases
│ └── themes/
│ ├── light.json # Light theme overrides
│ └── dark.json # Dark theme overrides
├── tokens.resolver.json # DTCG resolver -- ties everything together
├── build.ts # Build script
├── tsconfig.json
└── package.json

tokens/base/colors.json defines your foundational color palette:

{
"color": {
"base": {
"blue": {
"$type": "color",
"$value": { "colorSpace": "srgb", "components": [0, 0.4, 0.8] }
},
"white": {
"$type": "color",
"$value": { "colorSpace": "srgb", "components": [1, 1, 1] }
},
"gray-900": {
"$type": "color",
"$value": { "colorSpace": "srgb", "components": [0.1, 0.11, 0.12] }
}
}
}
}

tokens/semantic/colors.json creates purpose-driven names that reference the base tokens:

{
"color": {
"semantic": {
"primary": {
"$type": "color",
"$value": "{color.base.blue}"
},
"background": {
"$type": "color",
"$value": "{color.base.white}"
},
"text": {
"$type": "color",
"$value": "{color.base.gray-900}"
}
}
}
}

tokens.resolver.json is the DTCG resolver that tells Dispersa which token files to load and how modifiers (like themes) are applied:

{
"version": "2025.10",
"sets": {
"colors": {
"sources": [
{ "$ref": "./tokens/base/colors.json" },
{ "$ref": "./tokens/semantic/colors.json" }
]
}
},
"modifiers": {
"theme": {
"default": "light",
"contexts": {
"light": [{ "$ref": "./tokens/themes/light.json" }],
"dark": [{ "$ref": "./tokens/themes/dark.json" }]
}
}
},
"resolutionOrder": [{ "$ref": "#/sets/colors" }, { "$ref": "#/modifiers/theme" }]
}

build.ts creates a Dispersa instance, points it at the resolver, and defines CSS output with transforms:

import { Dispersa, css } from 'dispersa'
import { colorToHex, nameKebabCase } from 'dispersa/transforms'
const dispersa = new Dispersa({
resolver: './tokens.resolver.json',
buildPath: './output',
})
const result = await dispersa.build({
outputs: [
css({
name: 'css',
file: 'tokens.css',
preset: 'bundle',
preserveReferences: true,
transforms: [nameKebabCase(), colorToHex()],
}),
],
})
if (result.success) {
console.log('Build complete!')
}
Terminal window
pnpm run build

This executes tsx build.ts, which resolves your tokens, applies transforms, and writes the output.

Open output/tokens.css. You should see your base tokens and theme overrides rendered as CSS custom properties:

:root {
--color-base-blue: #0066cc;
--color-base-white: #ffffff;
--color-base-gray-900: #1a1c1f;
--color-semantic-primary: var(--color-base-blue);
--color-semantic-background: var(--color-base-white);
--color-semantic-text: var(--color-base-gray-900);
/* ... */
}
[data-theme='dark'] {
--color-semantic-background: var(--color-base-gray-900);
--color-semantic-text: var(--color-base-white);
}

Because preserveReferences is true, semantic tokens emit var() references to their base tokens instead of inlining the resolved hex value. The bundle preset puts the base values in :root and the dark theme overrides in a [data-theme="dark"] selector — all in a single file.

From here you can:

  • Edit tokens — add new values to tokens/base/colors.json or create new token files and reference them in the resolver
  • Add transforms — e.g. dimensionToPx() or colorToRgb() from dispersa/transforms
  • Add outputs — e.g. json() or js() alongside css()
  • Switch to the CLI — replace build.ts with a dispersa.config.ts and run dispersa build