Color.DesignTokens (Color v0.11.0)

Copy Markdown

Encode and decode W3C Design Tokens Community Group color tokens, per the October 2025 draft of the DTCG Color spec.

Design tokens are a portable JSON format for design-system values. A color token looks like this:

%{
  "$type" => "color",
  "$value" => %{
    "colorSpace" => "oklch",
    "components" => [0.63, 0.19, 259.5],
    "alpha" => 1.0,
    "hex" => "#3b82f6"
  }
}

This module converts between those maps and the library's Color.* structs, so palettes can round-trip through any tool that speaks the DTCG format (Style Dictionary, Figma, Penpot, and so on).

Encode

iex> {:ok, color} = Color.new("#3b82f6")
iex> token = Color.DesignTokens.encode(color)
iex> token["colorSpace"]
"srgb"

To emit in a different colour space, convert first:

iex> {:ok, oklch} = Color.convert("#3b82f6", Color.Oklch)
iex> Color.DesignTokens.encode(oklch) |> Map.get("colorSpace")
"oklch"

Or use encode/2 with the :space option — it runs the conversion for you:

iex> token = Color.DesignTokens.encode("#3b82f6", space: Color.Oklch)
iex> token["colorSpace"]
"oklch"

Decode

iex> value = %{"colorSpace" => "oklch", "components" => [0.63, 0.19, 259.5], "alpha" => 1}
iex> {:ok, oklch} = Color.DesignTokens.decode(value)
iex> match?(%Color.Oklch{}, oklch)
true

Decode also accepts full tokens (with $type / $value), and a hex-only fallback:

iex> {:ok, srgb} = Color.DesignTokens.decode(%{"hex" => "#3b82f6"})
iex> Color.to_hex(srgb)
"#3b82f6"

Unknown colour spaces or malformed input return {:error, %Color.DesignTokensDecodeError{}}.

Supported colour spaces

The DTCG spec and our mapping:

DTCG colorSpaceStruct
"srgb"Color.SRGB
"srgb-linear"Color.RGB with working_space: :SRGB
"hsl"Color.HSL
"lab"Color.Lab
"lch"Color.LCHab
"oklab"Color.Oklab
"oklch"Color.Oklch
"display-p3"Color.RGB with working_space: :P3
"rec2020"Color.RGB with working_space: :Rec2020
"prophoto-rgb"Color.RGB with working_space: :ProPhoto
"a98-rgb"Color.AdobeRGB
"xyz-d50"Color.XYZ with illuminant: :D50
"xyz-d65" / "xyz"Color.XYZ with illuminant: :D65

Alias tokens

DTCG supports alias tokens — "$value" => "{palette.blue.500}" — that reference another token. This module does not resolve aliases. Resolve them in the caller (where you have the full token tree) before passing values here. decode/1 returns a specific :alias_not_resolved error if it sees one, so you can handle them cleanly.

Summary

Functions

Decodes a DTCG color token (or $value map) into a Color.* struct.

Like decode/1 but raises on error.

Encodes a colour into a DTCG $value map.

Encodes a colour and wraps it as a full DTCG color token (%{"$type" => "color", "$value" => ...}).

Functions

decode(token)

@spec decode(map() | binary()) :: {:ok, struct()} | {:error, Exception.t()}

Decodes a DTCG color token (or $value map) into a Color.* struct.

Arguments

  • token is either a full token (%{"$type" => "color", "$value" => ...}) or a bare $value map. Alias strings ("{path.to.token}") are explicitly rejected — see the module doc.

Returns

  • {:ok, struct} on success.

  • {:error, %Color.DesignTokensDecodeError{}} on failure.

Examples

iex> {:ok, oklch} = Color.DesignTokens.decode(%{"colorSpace" => "oklch", "components" => [0.5, 0.1, 180]})
iex> Float.round(oklch.l, 2)
0.5

iex> {:error, %Color.DesignTokensDecodeError{reason: :unknown_color_space}} =
...>   Color.DesignTokens.decode(%{"colorSpace" => "fake-space", "components" => [0, 0, 0]})

decode!(token)

@spec decode!(map() | binary()) :: struct()

Like decode/1 but raises on error.

Examples

iex> Color.DesignTokens.decode!(%{"hex" => "#3b82f6"}) |> Color.to_hex()
"#3b82f6"

encode(color, options \\ [])

@spec encode(
  Color.input() | struct(),
  keyword()
) :: map()

Encodes a colour into a DTCG $value map.

Arguments

  • color is anything accepted by Color.new/1 or already a Color.* struct.

Options

  • :space is the target colour space for the encoded token — any module accepted by Color.convert/2. Defaults to the colour's current space (no conversion).

Returns

  • A DTCG $value map with "colorSpace", "components", optional "alpha", and "hex" fallback.

Examples

iex> token = Color.DesignTokens.encode("#3b82f6")
iex> token["colorSpace"]
"srgb"

iex> token = Color.DesignTokens.encode("#3b82f6", space: Color.Oklch)
iex> token["colorSpace"]
"oklch"

encode_token(color, options \\ [])

@spec encode_token(
  Color.input() | struct(),
  keyword()
) :: map()

Encodes a colour and wraps it as a full DTCG color token (%{"$type" => "color", "$value" => ...}).

Arguments

Options

See encode/2.

Returns

  • A DTCG token map with $type and $value.

Examples

iex> token = Color.DesignTokens.encode_token("#3b82f6")
iex> token["$type"]
"color"
iex> token["$value"]["colorSpace"]
"srgb"