Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/style-dictionary-token-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hebilicious/cssforge": minor
---

Add Style Dictionary token JSON output, with resolved values by default and an optional CSS-variable mode for usage matching.
130 changes: 127 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1027,21 +1027,145 @@ pnpm run cssforge
# Watch mode
pnpm run cssforge -- --watch

# Custom paths and output
pnpm run cssforge -- --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --mode all
# Custom paths and output
pnpm run cssforge -- --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --style-dictionary ./dist/design-tokens.sd.json --mode all

# Style Dictionary JSON with final values (default)
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./dist/design-tokens.sd.json

# Keep CSS variables as values for usage matching
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./dist/design-tokens.sd.json --style-dictionary-value-mode css-reference
```

## Programmatic Usage

You can also use CSS Forge programmatically:

```typescript
import { generateCSS } from "jsr:@hebilicious/cssforge";
import { generateCSS, generateStyleDictionaryJSON } from "jsr:@hebilicious/cssforge";

// Generate CSS string
const css = generateCSS(config);

// Write final values for Style Dictionary
const resolvedTokens = generateStyleDictionaryJSON(config);

// Keep var(--token) as each token's value for usage matching
const usageTokens = generateStyleDictionaryJSON(config, { valueMode: "css-reference" });
```

## Style Dictionary JSON

CSS Forge can generate a separate token file for Style Dictionary and other tools that read
the same JSON shape. This output does not change the CSS, TypeScript, or regular JSON files
you already generate.

### Generate the file

Use `style-dictionary` mode to generate only the token file:

```bash
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./.cssforge/tokens.json
```

Use `--mode all` to generate it together with the CSS, TypeScript, and regular JSON outputs.
The `--style-dictionary` option controls where the token file is written.

A generated token looks like this:

```json
{
"palette": {
"neutral": {
"900": {
"value": "oklch(17.764% 0 0)",
"type": "color",
"$tier": "primitive",
"$resolvedValue": "oklch(17.764% 0 0)",
"attributes": {
"cssVariable": "--palette-neutral-900",
"cssVariableReference": "var(--palette-neutral-900)",
"tailwindVariable": "--palette-neutral-900",
"resolvedValue": "oklch(17.764% 0 0)",
"sourcePath": "palette.neutral.900"
}
}
}
}
}
```

Semantic tokens also include `$reference` and `attributes.referencePaths`. These paths match
the keys in the generated file, so consumers can connect a semantic token to its source.

### Token fields

| Field | Contains | Use it for |
| --- | --- | --- |
| `value` | The token value in the selected value mode | Rendering and Style Dictionary transforms |
| `type` | The value kind, falling back to the CSS Forge module when the value kind is not narrower | Grouping and previews that depend on what the token holds |
| `$tier` | `primitive` or `semantic` | Separating base scales from intent tokens |
| `$reference` | The token path this token was built from, when it has one | Following a semantic token back to its source |
| `attributes.cssVariable` | The token's CSS custom property, such as `--palette-neutral-900` | Declaring or overriding the token in CSS |
| `attributes.tailwindVariable` | The same custom property name, without the `var()` wrapper | Tools that match authored `var(--token)` usage to tokens |
| `attributes.resolvedValue` | The final value, even in `css-reference` mode | Showing a value without following references |
| `$resolvedValue` | The same final value as a top-level DTCG-style field | Tools that read `$resolvedValue` before falling back to `value` |

`type` narrows `fontSize`, `lineHeight`, `fontWeight`, `fontFamily`, `borderRadius`,
`letterSpacing`, `shadow`, `opacity`, `zIndex`, and `number` when the token's name and value
agree, and stays `color`, `spacing`, `gradient`, `typography`, `primitive`, or `component`
otherwise.

A narrowed kind is also matched by the leaf name aliases `font-size`, `text-size`,
`line-height`, `leading`, `font-weight`, `font-family`, `radius`, `rounded`, `tracking`,
`box-shadow`, `text-shadow`, `shadow`, `alpha`, `z-index`, `gap`, `duration`, and `delay`.

### Choose the value mode

| Mode | `value` contains | Use it for |
| --- | --- | --- |
| `resolved` (default) | The final value, such as `oklch(...)`, `1rem`, or `clamp(...)` | Style Dictionary transforms and token previews |
| `css-reference` | The token's own CSS variable, such as `var(--palette-neutral-900)` | Tools that match CSS variable usage in source files |

The default `resolved` mode recursively resolves references to other CSS Forge tokens. Cycles
and unknown CSS variables remain as `var(...)` instead of causing generation to fail.

`css-reference` values are CSS custom-property references, not Style Dictionary aliases.
Style Dictionary aliases use `{path.to.token}`. Use the default `resolved` mode when Style
Dictionary will transform the file.

```bash
# Keep CSS variables as values for usage matching
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./.cssforge/tokens.json --style-dictionary-value-mode css-reference
```

### Programmatic API

```typescript
import { generateStyleDictionaryJSON } from "jsr:@hebilicious/cssforge";

const resolvedTokens = generateStyleDictionaryJSON(config);
const usageTokens = generateStyleDictionaryJSON(config, {
valueMode: "css-reference",
});
```

### Example: Musea

Musea can use the generated file as its token source:

```typescript
import { musea } from "@vizejs/vite-plugin-musea";

musea({
tokensPath: ".cssforge/tokens.json",
});
```

Musea reads `value`, `type`, and `$reference` from this file. Keep the default `resolved` value
mode: previews and token swatches render from `value`, and `attributes.tailwindVariable` is what
lets Musea attribute a `var(--token)` written in an art file back to its token.

## Agentic usage

CSSForge is intentionally designed to be extremely simple and integrate well with various
Expand Down
130 changes: 127 additions & 3 deletions packages/cssforge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1027,21 +1027,145 @@ pnpm run cssforge
# Watch mode
pnpm run cssforge -- --watch

# Custom paths and output
pnpm run cssforge -- --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --mode all
# Custom paths and output
pnpm run cssforge -- --config ./foo/bar/custom-path.ts --css ./dist/design-tokens.css --ts ./dist/design-tokens.ts --json ./dist/design-tokens.json --style-dictionary ./dist/design-tokens.sd.json --mode all

# Style Dictionary JSON with final values (default)
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./dist/design-tokens.sd.json

# Keep CSS variables as values for usage matching
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./dist/design-tokens.sd.json --style-dictionary-value-mode css-reference
```

## Programmatic Usage

You can also use CSS Forge programmatically:

```typescript
import { generateCSS } from "jsr:@hebilicious/cssforge";
import { generateCSS, generateStyleDictionaryJSON } from "jsr:@hebilicious/cssforge";

// Generate CSS string
const css = generateCSS(config);

// Write final values for Style Dictionary
const resolvedTokens = generateStyleDictionaryJSON(config);

// Keep var(--token) as each token's value for usage matching
const usageTokens = generateStyleDictionaryJSON(config, { valueMode: "css-reference" });
```

## Style Dictionary JSON

CSS Forge can generate a separate token file for Style Dictionary and other tools that read
the same JSON shape. This output does not change the CSS, TypeScript, or regular JSON files
you already generate.

### Generate the file

Use `style-dictionary` mode to generate only the token file:

```bash
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./.cssforge/tokens.json
```

Use `--mode all` to generate it together with the CSS, TypeScript, and regular JSON outputs.
The `--style-dictionary` option controls where the token file is written.

A generated token looks like this:

```json
{
"palette": {
"neutral": {
"900": {
"value": "oklch(17.764% 0 0)",
"type": "color",
"$tier": "primitive",
"$resolvedValue": "oklch(17.764% 0 0)",
"attributes": {
"cssVariable": "--palette-neutral-900",
"cssVariableReference": "var(--palette-neutral-900)",
"tailwindVariable": "--palette-neutral-900",
"resolvedValue": "oklch(17.764% 0 0)",
"sourcePath": "palette.neutral.900"
}
}
}
}
}
```

Semantic tokens also include `$reference` and `attributes.referencePaths`. These paths match
the keys in the generated file, so consumers can connect a semantic token to its source.

### Token fields

| Field | Contains | Use it for |
| --- | --- | --- |
| `value` | The token value in the selected value mode | Rendering and Style Dictionary transforms |
| `type` | The value kind, falling back to the CSS Forge module when the value kind is not narrower | Grouping and previews that depend on what the token holds |
| `$tier` | `primitive` or `semantic` | Separating base scales from intent tokens |
| `$reference` | The token path this token was built from, when it has one | Following a semantic token back to its source |
| `attributes.cssVariable` | The token's CSS custom property, such as `--palette-neutral-900` | Declaring or overriding the token in CSS |
| `attributes.tailwindVariable` | The same custom property name, without the `var()` wrapper | Tools that match authored `var(--token)` usage to tokens |
| `attributes.resolvedValue` | The final value, even in `css-reference` mode | Showing a value without following references |
| `$resolvedValue` | The same final value as a top-level DTCG-style field | Tools that read `$resolvedValue` before falling back to `value` |

`type` narrows `fontSize`, `lineHeight`, `fontWeight`, `fontFamily`, `borderRadius`,
`letterSpacing`, `shadow`, `opacity`, `zIndex`, and `number` when the token's name and value
agree, and stays `color`, `spacing`, `gradient`, `typography`, `primitive`, or `component`
otherwise.

A narrowed kind is also matched by the leaf name aliases `font-size`, `text-size`,
`line-height`, `leading`, `font-weight`, `font-family`, `radius`, `rounded`, `tracking`,
`box-shadow`, `text-shadow`, `shadow`, `alpha`, `z-index`, `gap`, `duration`, and `delay`.

### Choose the value mode

| Mode | `value` contains | Use it for |
| --- | --- | --- |
| `resolved` (default) | The final value, such as `oklch(...)`, `1rem`, or `clamp(...)` | Style Dictionary transforms and token previews |
| `css-reference` | The token's own CSS variable, such as `var(--palette-neutral-900)` | Tools that match CSS variable usage in source files |

The default `resolved` mode recursively resolves references to other CSS Forge tokens. Cycles
and unknown CSS variables remain as `var(...)` instead of causing generation to fail.

`css-reference` values are CSS custom-property references, not Style Dictionary aliases.
Style Dictionary aliases use `{path.to.token}`. Use the default `resolved` mode when Style
Dictionary will transform the file.

```bash
# Keep CSS variables as values for usage matching
pnpm run cssforge -- --mode style-dictionary --style-dictionary ./.cssforge/tokens.json --style-dictionary-value-mode css-reference
```

### Programmatic API

```typescript
import { generateStyleDictionaryJSON } from "jsr:@hebilicious/cssforge";

const resolvedTokens = generateStyleDictionaryJSON(config);
const usageTokens = generateStyleDictionaryJSON(config, {
valueMode: "css-reference",
});
```

### Example: Musea

Musea can use the generated file as its token source:

```typescript
import { musea } from "@vizejs/vite-plugin-musea";

musea({
tokensPath: ".cssforge/tokens.json",
});
```

Musea reads `value`, `type`, and `$reference` from this file. Keep the default `resolved` value
mode: previews and token swatches render from `value`, and `attributes.tailwindVariable` is what
lets Musea attribute a `var(--token)` written in an art file back to its token.

## Agentic usage

CSSForge is intentionally designed to be extremely simple and integrate well with various
Expand Down
Loading
Loading