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
- Compile-time generation — Tokens become pattern-matched functions, not data
- Multi-theme support — Light/dark/custom themes with zero runtime overhead
- DTCG format — Follows the Design Tokens Community Group specification
- Token references —
{path.to.token}and JSON Pointer (#/path/to/token) resolution with circular-reference detection - Structural inheritance —
$extendsmerges parent groups into children with deep override semantics - Type-based transforms — Apply functions to tokens by their
$type(e.g. downcase all colors, convert px to rem) - Type inheritance —
$typepropagates from parent groups to descendant tokens, with override at any level - Fast lookups — O(1) access via pattern matching, not map traversal
Installation
Add jetons to your dependencies in mix.exs:
def deps do
[
{:jetons, "~> 0.1.1"},
{:jason, "~> 1.4"} # Required for JSON parsing
]
endQuick 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 themeToken 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:
"colors.brand.primary"→"#1B66B3""colors.grey.500"→"#676767""spacing.small"→"8px"
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: nilThis means:
- Token access is O(1) — Direct function call with pattern matching
- No runtime overhead — All processing happens at compile time
- Memory efficient — Functions are code, not data
License
MIT