Jetons

A compile-time design token library for Elixir.

Jetons generates fast, type-safe accessor functions from design token JSON files at compile time. Instead of runtime map lookups, tokens become individual function clauses that use pattern matching for maximum performance.

Features

Installation

Add jetons to your dependencies in mix.exs:

def deps do
  [
    {:jetons, "~> 0.1.1"},
    {:jason, "~> 1.4"}  # Required for JSON parsing
  ]
end

Quick Start

Single Theme

defmodule MyApp.Tokens do
  use Jetons,
    main: File.read!("tokens.json") |> Jason.decode!()
end

MyApp.Tokens.token("colors.primary")       # => "#1B66B3"
MyApp.Tokens.token!("colors.primary")      # => "#1B66B3" (raises if not found)
MyApp.Tokens.list_colors()                 # => [{"colors.primary", "#1B66B3"}, ...]

Multiple Themes

defmodule MyApp.Tokens do
  use Jetons,
    light: File.read!("tokens/light.json") |> Jason.decode!(),
    dark: File.read!("tokens/dark.json") |> Jason.decode!()
end

MyApp.Tokens.token("colors.background")            # Uses first theme (light)
MyApp.Tokens.token("colors.background", :dark)     # Explicit theme
MyApp.Tokens.themes()                               # => [:light, :dark]
MyApp.Tokens.list_colors(:dark)                     # List colors for dark theme

Token Format

Jetons expects tokens in DTCG format: nested maps where leaf nodes have "$value" keys. Keys starting with $ ($type, $description, etc.) are metadata and filtered from the output.

{
  "colors": {
    "$type": "color",
    "brand": {
      "primary": { "$value": "#1B66B3" }
    },
    "grey": {
      "500": { "$value": "#676767" }
    }
  },
  "spacing": {
    "small": { "$value": "8px" }
  }
}

Nested keys become dot-notation paths:

Token References

Tokens can reference other tokens using curly-brace syntax or JSON Pointers. References are resolved transitively, and circular references raise at compile time.

{
  "colors": {
    "palette": { "red": { "$value": "#FF0000" } },
    "brand":   { "$value": "{colors.palette.red}" },
    "button":  { "$value": "#/colors/brand" }
  }
}

Both colors.brand and colors.button resolve to "#FF0000".

Structural Inheritance ($extends)

Groups can inherit tokens from a parent group using $extends. Child properties override inherited ones, and $extends chains are resolved transitively.

{
  "colors": {
    "base": {
      "$type": "color",
      "primary": { "$value": "#FF0000" }
    },
    "brand": {
      "$extends": "colors.base",
      "secondary": { "$value": "#00FF00" }
    }
  }
}

colors.brand inherits $type and primary from colors.base, and adds its own secondary. Cross-category extends (e.g. "brand.palette" extending "colors.base") is also supported.

Type-Based Transforms

The $type field propagates from parent groups to descendant tokens (a child can override with its own $type). You can then apply transform functions that target specific types:

defmodule MyApp.Tokens do
  use Jetons,
    transforms: [
      {"color", fn v -> String.downcase(v) end},
      {"dimension", fn v -> String.replace(v, "px", "rem") end}
    ],
    main: %{
      "colors" => %{
        "$type" => "color",
        "primary" => %{"$value" => "#FF0000"}
      },
      "spacing" => %{
        "$type" => "dimension",
        "small" => %{"$value" => "8px"}
      }
    }
end

MyApp.Tokens.token("colors.primary")  # => "#ff0000"
MyApp.Tokens.token("spacing.small")   # => "8rem"

Transforms run after reference resolution, so referenced values are transformed correctly. Tokens whose type doesn't match any transform pass through unchanged.

Generated API

Single Theme

Function Description
token/1 Get token value by path (returns nil if not found)
token!/1 Get token value by path (raises KeyError if not found)
list_<category>/0 List all tokens in a category as {path, value} tuples
group_by_path/2 Group tokens by path depth into a nested map
get_groups/1 Get sorted list of top-level groups for a category
get_in_group/2 Get all tokens in a specific group as {key, value} tuples

Multi-Theme

All single-theme functions plus:

Function Description
token/2 Get token value with explicit theme parameter
token!/2 Get token value with theme (raises if not found)
list_<category>/1 List category tokens for a specific theme
themes/0 List all available theme names
group_by_path/3 Group tokens by path depth for a specific theme
get_groups/2 Get top-level groups for a category and theme
get_in_group/3 Get tokens in a group for a specific theme

Parser Functions

These are available on the Jetons module directly for runtime use:

Function Description
from_config/1 Flatten a DTCG config map into {path, value} tuples
from_config/2 Same, with options (e.g. transforms:)
parse_tokens/1 Parse a JSON string or file path into token tuples
extract_categories/1 Extract sorted unique category names from token tuples
extract_type_map/1 Build a %{path => type} map with $type inheritance
resolve_extends/1 Resolve $extends inheritance in a config map
resolve_token_refs/1 Resolve {ref} and #/pointer references in token tuples
flatten_tokens/2 Flatten a nested map under a given prefix
deep_merge/2 Recursively merge two maps (right side wins)

Performance

Jetons generates individual function clauses for each token:

def token("colors.primary"), do: "#1B66B3"
def token("colors.secondary"), do: "#FCE531"
# ... one clause per token
def token(_), do: nil

This means:

License

MIT

Links