Jetons generates CSS custom properties from DTCG token files. It handles literal values, token references as var(), structured value types (colors, dimensions, shadows, etc.), multi-modifier diff blocks, composite utility classes, and custom modifier selectors.

Entry Points

There are three ways to generate CSS:

FunctionInputUse case
Jetons.CSS.generate/2Module that use JetonsCompile-time tokens
Jetons.CSS.from_config/2Raw DTCG config mapSingle file, no resolver
Jetons.CSS.from_resolver/3Resolver document + base dirMulti-file, multi-modifier

The mix jetons.build task wraps these behind a config-driven transformer pipeline.

Basic Output

A flat token file:

{
  "color": {
    "brand": { "$value": "#1B66B3" },
    "text": { "$value": "{color.brand}" }
  },
  "spacing": {
    "small": { "$value": "8px" }
  }
}

Produces:

:root {
  --color-brand: #1B66B3;
  --color-text: var(--color-brand);
  --spacing-small: 8px;
}

References become var() calls. The :root selector is configurable via the :selector option.

Inlining

The :inline option takes a list of path prefixes. Tokens matching these prefixes are omitted from the output, and references to them resolve to literal values instead of var().

Jetons.CSS.from_config(config, inline: ["color.palette"])

This is useful for primitive scales that shouldn't appear as custom properties but whose values should be inlined wherever referenced.

Structured Value Types

Jetons serializes DTCG structured values to their CSS equivalents:

DTCG typeExample $valueCSS output
Color (sRGB){"colorSpace": "srgb", "components": [1, 0, 0]}color(srgb 1 0 0)
Color (HSL){"colorSpace": "hsl", "components": [330, 100, 50]}hsl(330 100% 50%)
Color (OKLCH){"colorSpace": "oklch", "components": [0.7, 0.32, 328]}oklch(0.7 0.32 328)
Dimension{"value": 8, "unit": "px"}8px
Duration{"value": 200, "unit": "ms"}200ms
Font family["Helvetica Neue", "Arial", "sans-serif"]"Helvetica Neue", "Arial", sans-serif
Cubic bezier[0.42, 0, 1, 1]cubic-bezier(0.42, 0, 1, 1)
Number1.51.5
Shadow{"offsetX": ..., "offsetY": ..., "blur": ..., "spread": ..., "color": ...}2px 2px 4px 0px color(...)
Border{"width": ..., "style": ..., "color": ...}3px solid color(...)
Transition{"duration": ..., "delay": ..., "timingFunction": ...}200ms 0ms cubic-bezier(...)
Gradient[{"color": ..., "position": 0}, ...]linear-gradient(color(...) 0%, ...)

Alpha channels are included when present and not equal to 1. The "none" keyword is preserved for color components.

Multi-Modifier Output

from_resolver/3 generates a base :root block using default modifier values, then a diff-only block for each non-default context. Only tokens whose values actually differ from the base appear in diff blocks.

{
  "modifiers": {
    "theme": {
      "default": "light",
      "contexts": {
        "light": [{"$ref": "light.json"}],
        "dark": [{"$ref": "dark.json"}]
      }
    }
  }
}
:root {
  --color-bg: #FFFFFF;
  --color-text: #000000;
}

.theme-dark {
  --color-bg: #000000;
  --color-text: #FFFFFF;
}

Pinning modifiers via the :input option bakes them into the base resolution and skips their expansion:

Jetons.CSS.from_resolver(doc, dir, input: %{"brand" => "acme"})

Custom Set Selectors

Sets can also use $extensions.dev.jetons.css to emit their tokens under a custom selector, separate from the :root block. This is useful for CSS at-rules like @theme (Tailwind v4) or @layer where tokens need to be registered at a specific cascade level.

{
  "sets": {
    "hue-defaults": {
      "$extensions": {
        "dev.jetons.css": {
          "selector": "@theme"
        }
      },
      "sources": [{"$ref": "hue_grey.json"}]
    }
  }
}

This produces:

@theme {
  --color-hue-50: var(--grey-50);
  --color-hue-500: var(--grey-500);
  /* ... neutral defaults */
}

:root {
  /* ... full resolution including modifier overrides */
}

Set extension blocks appear before the :root block in the output. The set's tokens are still included in the full resolution (so modifiers can override them), but the set block itself only contains the tokens defined by that set's sources.

References within the set are resolved against the full base config, not just the set in isolation. This means a set can reference tokens from other sets (e.g. {grey.500} resolves to var(--grey-500)) without needing to include those sets in its own sources.

Custom Modifier Selectors

By default, modifier diff blocks use .{modifier}-{context} as their selector. The $extensions key on a modifier definition lets you customize this.

Selector Template

The selector field is a template string where {context} is replaced with the context name:

{
  "modifiers": {
    "shade": {
      "default": "dark",
      "$extensions": {
        "dev.jetons.css": {
          "selector": ".shade-{context}, .shade-{context} *"
        }
      },
      "contexts": {
        "dark": [{"$ref": "shade_dark.json"}],
        "light": [{"$ref": "shade_light.json"}]
      }
    }
  }
}

This produces:

.shade-light, .shade-light * {
  --color-bg: #FFFFFF;
  /* only tokens that differ from dark (the default) */
}

The descendant combinator (*) is a common pattern for design systems where shade should cascade to all children.

Emitting the Default Context

Normally the default context is only emitted under :root. Setting emitDefault to true generates an additional block for the default context under its named selector:

{
  "$extensions": {
    "dev.jetons.css": {
      "selector": ".hue-{context}",
      "emitDefault": true
    }
  }
}

With "default": "blue", this produces both :root { ... } (base resolution) and .hue-blue { ... } (full block with same values). This is useful when the default needs to work as an explicit class selector, not just as the implicit :root state.

Extension Namespace

Extensions use the dev.jetons.css vendor namespace under the standard DTCG $extensions mechanism (spec section 4.3). This keeps the configuration portable -- tools that don't understand the extension simply ignore it.

Composite Utility Classes

Two composite token types render as CSS utility class blocks instead of custom properties:

Typography ($type: "typography")

The standard DTCG typography composite type (spec section 9.8). Sub-values map to CSS properties:

DTCG keyCSS property
fontFamilyfont-family
fontSizefont-size
fontWeightfont-weight
letterSpacingletter-spacing
lineHeightline-height
{
  "typography-display": {
    "$type": "typography",
    "$value": {
      "fontFamily": "{font.display}",
      "fontSize": "{text.display}",
      "fontWeight": 900,
      "lineHeight": "{text.display--line-height}",
      "letterSpacing": "{text.display--letter-spacing}"
    }
  }
}
.typography-display {
  font-family: var(--font-display);
  font-size: var(--text-display);
  font-weight: 900;
  letter-spacing: var(--text-display--letter-spacing);
  line-height: var(--text-display--line-height);
}

Reference sub-values ("{font.display}") become var() calls. Literal sub-values (numbers, strings) are output directly. The token path becomes the class name with dots replaced by dashes.

Surface ($type: "x-surface")

A non-standard composite type for pairing background and text colors. The x- prefix follows the convention for vendor-specific extensions to avoid collision with future DTCG types.

DTCG keyCSS property
backgroundbackground
colorcolor
{
  "surface-background": {
    "$type": "x-surface",
    "$value": {
      "background": "{color.surface.background}",
      "color": "{color.text.on-background}"
    }
  }
}
.surface-background {
  background: var(--color-surface-background);
  color: var(--color-text-on-background);
}

Behavior in Modifier Diff Blocks

Utility class tokens are excluded from modifier diff blocks. Since their sub-values reference other tokens via var(), they inherit the correct values automatically when the referenced custom properties change. The utility classes only appear once in the base block output.

Dangling References

Composite sub-values can reference tokens that don't exist in the token system. For example, "{font.display}" renders as var(--font-display) even if no font.display token is defined. This is intentional -- font-family values often come from outside the design token system (web font loaders, CSS @font-face rules, etc.).

Transformer

The Jetons.CSS.Transformer module implements the Jetons.Transformer behaviour for use with mix jetons.build. It produces the same output as Jetons.CSS.from_config/2 and supports the same utility class and inlining features.

config :jetons,
  css: [
    transformer: Jetons.CSS.Transformer,
    resolver: "myapp/myapp.resolver.json",
    output: "priv/static/tokens.css",
    inline: ["palette"],
    selector: ":root"
  ]