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.

Returns the list of role names supported by role/3.

Emits the theme as a W3C Design Tokens Community Group token file.

Types

t()

@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

gamut_report(theme, working_space \\ :SRGB)

@spec gamut_report(t(), Color.Types.working_space()) :: map()

Returns a detailed gamut report broken down by sub-palette.

Arguments

Returns

  • A map with:
    • :working_space — the space checked against.

    • :in_gamut?true if every stop in every sub-palette is inside.

    • :sub_palettes — map of sub-palette key → Color.Palette.Tonal.gamut_report/2 result.

    • :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]

in_gamut?(theme, working_space \\ :SRGB)

@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

  • theme is a Color.Palette.Theme struct.

  • working_space is 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

new(seed, options \\ [])

@spec new(
  Color.input(),
  keyword()
) :: t()

Generates a complete theme from a seed colour.

Arguments

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 what role/2 expects. 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. Default 0.33.

  • :tertiary_hue_rotation — how many degrees to rotate the hue by for the tertiary palette. Default 60.0.

  • :neutral_chroma — absolute Oklch chroma for the neutral scale. Default 0.02.

  • :neutral_variant_chroma — absolute Oklch chroma for the neutral-variant scale. Default 0.04.

  • :light_anchor, :dark_anchor, :hue_drift, :gamut — passed through to each tonal scale. See Color.Palette.Tonal.

  • :name — optional string label stored on the struct.

Returns

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

role(theme, role, options \\ [])

@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

Options

  • :scheme is :light (default) or :dark. The dark scheme uses different stops to maintain contrast against a dark background.

Returns

  • {:ok, %Color.SRGB{}} for known roles, :error for 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

roles()

@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

to_tokens(theme, options \\ [])

@spec to_tokens(
  t(),
  keyword()
) :: map()

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

Options

  • :space is the colour space for emitted stop values. Any module accepted by Color.convert/2. Default Color.Oklch.

  • :scheme is :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}"