Color.Palette.Theme
(Color v0.11.0)
Copy Markdown
A complete theme — a coordinated set of five tonal scales — generated from a single seed colour.
Inspired by Material Design 3 and Material You's dynamic
theming. From one seed, this module produces five related
Color.Palette.Tonal palettes:
:primary— the seed's hue at full chroma. The main brand colour and the accent for interactive elements.:secondary— the seed's hue at reduced chroma (default ⅓). A quieter accent for secondary actions.:tertiary— the seed's hue rotated by a fixed angle (default +60°) at full chroma. A complementary accent.:neutral— the seed's hue at very low chroma (default 0.02). For surfaces, backgrounds, and text.:neutral_variant— the seed's hue at slightly higher chroma (default 0.04). For outlines and dividers.
Each of the five palettes has its own 13-stop tonal scale (or whatever stops were configured), so one seed yields ~65 colours covering every role a typical component library needs.
Material roles
role/2 maps a symbolic role name to a specific stop in one of
the five palettes, following Material 3's role tokens:
iex> theme = Color.Palette.Theme.new("#3b82f6")
iex> {:ok, primary} = Color.Palette.Theme.role(theme, :primary)
iex> {:ok, on_primary} = Color.Palette.Theme.role(theme, :on_primary)
iex> match?(%Color.SRGB{}, primary) and match?(%Color.SRGB{}, on_primary)
true
Summary
Functions
Returns a detailed gamut report broken down by sub-palette.
Returns true when every stop across all five sub-palettes
(primary, secondary, tertiary, neutral, neutral-variant) is
inside the given RGB working space.
Generates a complete theme from a seed colour.
Looks up a Material 3 role name in the theme.
Emits the theme as a W3C Design Tokens Community Group token file.
Types
@type t() :: %Color.Palette.Theme{ name: binary() | nil, neutral: Color.Palette.Tonal.t(), neutral_variant: Color.Palette.Tonal.t(), options: keyword(), primary: Color.Palette.Tonal.t(), secondary: Color.Palette.Tonal.t(), seed: Color.SRGB.t(), tertiary: Color.Palette.Tonal.t() }
Functions
@spec gamut_report(t(), Color.Types.working_space()) :: map()
Returns a detailed gamut report broken down by sub-palette.
Arguments
themeis aColor.Palette.Themestruct.working_spacedefaults to:SRGB.
Returns
- A map with:
:working_space— the space checked against.:in_gamut?—trueif every stop in every sub-palette is inside.:sub_palettes— map of sub-palette key →Color.Palette.Tonal.gamut_report/2result.:out_of_gamut— flat list of%{sub_palette, label, color}for stops that failed, across every sub-palette.
Examples
iex> theme = Color.Palette.Theme.new("#3b82f6")
iex> report = Color.Palette.Theme.gamut_report(theme, :SRGB)
iex> report.in_gamut?
true
iex> Map.keys(report.sub_palettes) |> Enum.sort()
[:neutral, :neutral_variant, :primary, :secondary, :tertiary]
@spec in_gamut?(t(), Color.Types.working_space()) :: boolean()
Returns true when every stop across all five sub-palettes
(primary, secondary, tertiary, neutral, neutral-variant) is
inside the given RGB working space.
Arguments
themeis aColor.Palette.Themestruct.working_spaceis an RGB working-space atom. Defaults to:SRGB.
Returns
- A boolean.
Examples
iex> theme = Color.Palette.Theme.new("#3b82f6")
iex> Color.Palette.Theme.in_gamut?(theme, :SRGB)
true
@spec new( Color.input(), keyword() ) :: t()
Generates a complete theme from a seed colour.
Arguments
seedis anything accepted byColor.new/1.
Options
:stops— the stop list for each of the five tonal scales. Defaults to Material 3's[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100], which is whatrole/2expects. Override only if you understand that role lookups may then fail.:secondary_chroma_factor— how much to multiply the seed's chroma by for the secondary palette. Default0.33.:tertiary_hue_rotation— how many degrees to rotate the hue by for the tertiary palette. Default60.0.:neutral_chroma— absolute Oklch chroma for the neutral scale. Default0.02.:neutral_variant_chroma— absolute Oklch chroma for the neutral-variant scale. Default0.04.:light_anchor,:dark_anchor,:hue_drift,:gamut— passed through to each tonal scale. SeeColor.Palette.Tonal.:name— optional string label stored on the struct.
Returns
- A
Color.Palette.Themestruct.
Examples
iex> theme = Color.Palette.Theme.new("#3b82f6", name: "brand")
iex> theme.name
"brand"
iex> match?(%Color.Palette.Tonal{}, theme.primary)
true
iex> match?(%Color.Palette.Tonal{}, theme.neutral)
true
@spec role(t(), atom(), keyword()) :: {:ok, Color.SRGB.t()} | :error
Looks up a Material 3 role name in the theme.
Roles are the symbolic tokens used by Material components —
:primary, :on_primary, :surface, :outline, etc. Each
role maps to a specific stop in one of the five palettes.
Arguments
themeis aColor.Palette.Themestruct.roleis a role atom (see Material 3 tokens).
Options
:schemeis:light(default) or:dark. The dark scheme uses different stops to maintain contrast against a dark background.
Returns
{:ok, %Color.SRGB{}}for known roles,:errorfor unknown roles.
Examples
iex> theme = Color.Palette.Theme.new("#3b82f6")
iex> {:ok, %Color.SRGB{}} = Color.Palette.Theme.role(theme, :primary)
iex> {:ok, %Color.SRGB{}} = Color.Palette.Theme.role(theme, :primary, scheme: :dark)
iex> Color.Palette.Theme.role(theme, :nonsense)
:error
@spec roles() :: [atom()]
Returns the list of role names supported by role/3.
Returns
- A sorted list of role atoms.
Examples
iex> :primary in Color.Palette.Theme.roles()
true
iex> :surface in Color.Palette.Theme.roles()
true
Emits the theme as a W3C Design Tokens Community Group token file.
Produces two top-level groups:
"palette"— the five tonal scales (primary, secondary, tertiary, neutral, neutral-variant), each as a stop-keyed group of color tokens."role"— Material 3 role tokens (primary,on_primary,surface, etc.) emitted as DTCG alias tokens that reference the corresponding stop in"palette". Tools that resolve aliases will see both the raw palette and the semantic vocabulary.
Arguments
themeis aColor.Palette.Themestruct.
Options
:spaceis the colour space for emitted stop values. Any module accepted byColor.convert/2. DefaultColor.Oklch.:schemeis:light(default) or:dark. Controls which tone each role aliases to.
Returns
- A map with
"palette"and"role"keys.
Examples
iex> theme = Color.Palette.Theme.new("#3b82f6")
iex> tokens = Color.Palette.Theme.to_tokens(theme)
iex> tokens["palette"]["primary"]["40"]["$type"]
"color"
iex> tokens["role"]["primary"]["$value"]
"{palette.primary.40}"