Color.Palette
(Color v0.11.0)
Copy Markdown
Palette generation for design systems and web sites.
This module is the public façade for several palette-generation algorithms. Each algorithm lives in its own submodule and returns a struct with the generated colours and the parameters that produced them.
Algorithms
tonal/2— a single tonal scale (Tailwind / Radix / Open Color style) — one seed colour, N shades from light to dark, useful forbg-primary-50throughbg-primary-950design tokens.theme/2— a complete Material Design 3 style theme from one seed: five coordinated tonal scales (primary, secondary, tertiary, neutral, neutral-variant), addressable by Material role names like:on_primaryor:surface_variant.contrast/2— a contrast-targeted palette (Adobe Leonardo style) — shades that hit specific WCAG or APCA contrast ratios against a chosen background. Use this when you need accessibility-guaranteed component states.contrast_scale/2— a contrast-constrained tonal scale (Matt Ström-Awn's approach) — a numbered scale where any two stops ≥apartlabel units apart are guaranteed to satisfy a minimum contrast ratio. A hybrid betweentonalandcontrast.
Working space
All palette algorithms operate in Oklch, the cylindrical
variant of Oklab. Oklch is perceptually uniform for lightness,
which is exactly what tonal scales need: equal lightness steps
look like equal lightness steps to the eye. After generation,
each stop is gamut-mapped to sRGB via Color.Gamut.to_gamut/3
using the CSS Color 4 algorithm so that no stop ever falls
outside the displayable cube.
Summary
Functions
Generates a contrast-targeted palette — shades whose contrast
against a chosen background matches a list of target ratios.
See Color.Palette.Contrast for the full algorithm and option
list.
Generates a contrast-constrained tonal scale. See
Color.Palette.ContrastScale for the full algorithm.
Returns a detailed gamut report on the given palette.
Returns true if every stop in the given palette is inside
the chosen RGB working space.
Generates a colour in the given category while preserving the seed's perceived lightness and chroma.
Returns the full list of category atoms accepted by
semantic/3.
Generates a complete Material Design 3 style theme from a seed
colour. See Color.Palette.Theme for the full algorithm and
option list.
Generates a tonal scale — N shades of a single hue — from a seed
colour. See Color.Palette.Tonal for the full algorithm.
Functions
@spec contrast( Color.input(), keyword() ) :: Color.Palette.Contrast.t()
Generates a contrast-targeted palette — shades whose contrast
against a chosen background matches a list of target ratios.
See Color.Palette.Contrast for the full algorithm and option
list.
Arguments
seedis anything accepted byColor.new/1.
Options
See Color.Palette.Contrast.new/2.
Returns
- A
Color.Palette.Contraststruct.
Examples
iex> palette = Color.Palette.contrast("#3b82f6", targets: [4.5, 7.0])
iex> length(palette.stops)
2
@spec contrast_scale( Color.input(), keyword() ) :: Color.Palette.ContrastScale.t()
Generates a contrast-constrained tonal scale. See
Color.Palette.ContrastScale for the full algorithm.
Arguments
seedis anything accepted byColor.new/1.
Options
See Color.Palette.ContrastScale.new/2.
Returns
- A
Color.Palette.ContrastScalestruct.
Examples
iex> palette = Color.Palette.contrast_scale("#3b82f6")
iex> Map.keys(palette.stops) |> Enum.sort()
[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]
@spec gamut_report( struct(), Color.Types.working_space() ) :: map()
Returns a detailed gamut report on the given palette.
Dispatches on palette type — see each palette module's
gamut_report/2 for the returned map's exact shape.
Arguments
paletteis any palette struct produced by this module.working_spacedefaults to:SRGB.
Returns
- A map. The top-level
:in_gamut?key is present on every palette type.
Examples
iex> palette = Color.Palette.tonal("#3b82f6")
iex> report = Color.Palette.gamut_report(palette, :SRGB)
iex> report.in_gamut?
true
@spec in_gamut?( struct(), Color.Types.working_space() ) :: boolean()
Returns true if every stop in the given palette is inside
the chosen RGB working space.
Dispatches on the palette struct type, so works uniformly for
Color.Palette.Tonal, Color.Palette.Theme,
Color.Palette.Contrast, and Color.Palette.ContrastScale.
Intended primarily for CI checks — call once per palette and
fail the build if the result is false.
Arguments
paletteis any palette struct produced by this module.working_spaceis an RGB working-space atom. Defaults to:SRGB.
Returns
- A boolean.
Examples
iex> palette = Color.Palette.tonal("#3b82f6")
iex> Color.Palette.in_gamut?(palette)
true
iex> theme = Color.Palette.theme("#3b82f6")
iex> Color.Palette.in_gamut?(theme, :SRGB)
true
@spec semantic(Color.input(), atom(), keyword()) :: struct()
Generates a colour in the given category while preserving the seed's perceived lightness and chroma.
Useful for synthesising semantic colours — success, danger, warning, info — that feel like they belong to the same palette as a brand seed, without every brand needing hand-picked accents.
The algorithm is deliberately simple: convert the seed to Oklch, look up the category's canonical hue (e.g. red ≈ 25°, green ≈ 145°, blue ≈ 250°), build an Oklch colour at that hue with the seed's lightness and chroma, and gamut-map into sRGB. The output's saturation and perceived weight will match the seed, just at a different hue.
Once you have the semantic colour, feed it into
tonal/2, theme/2, contrast/2, or contrast_scale/2 to
produce a full scale for that semantic role.
Supported categories
Semantic aliases (UI vocabulary):
:success,:positive→ green:danger,:error,:destructive→ red:warning,:caution→ orange:info,:information→ blue:neutral→ strips almost all chroma, preserving the seed's hue as a subtle tint
Hue categories (direct names):
:red,:orange,:yellow,:green,:teal,:blue,:purple,:pink
See semantic_categories/0 for the authoritative list at
runtime.
Arguments
seedis anything accepted byColor.new/1.categoryis a semantic alias or hue-category atom (see above).
Options
:chroma_factormultiplies the seed's chroma before the output is built.1.0(default) preserves it,0.5mutes the result,0.0produces a grey at the new hue.:lightnessoverrides the output's lightness with a value in[0.0, 1.0]in Oklch. Defaults to the seed's lightness.:gamutis the RGB working space to map into. Default:SRGB.
Returns
- A colour struct in the chosen gamut (typically
%Color.SRGB{}).
Examples
iex> {:ok, _} = Color.new("#3b82f6")
iex> danger = Color.Palette.semantic("#3b82f6", :danger)
iex> {:ok, oklch} = Color.convert(danger, Color.Oklch)
iex> oklch.h >= 15 and oklch.h <= 40
true
iex> success = Color.Palette.semantic("#3b82f6", :success)
iex> {:ok, oklch} = Color.convert(success, Color.Oklch)
iex> oklch.h >= 130 and oklch.h <= 160
true
iex> neutral = Color.Palette.semantic("#3b82f6", :neutral)
iex> {:ok, oklch} = Color.convert(neutral, Color.Oklch)
iex> oklch.c < 0.05
true
@spec semantic_categories() :: [atom()]
Returns the full list of category atoms accepted by
semantic/3.
Returns
- A list of atoms in alphabetical order.
Examples
iex> categories = Color.Palette.semantic_categories()
iex> :success in categories
true
iex> :red in categories
true
@spec theme( Color.input(), keyword() ) :: Color.Palette.Theme.t()
Generates a complete Material Design 3 style theme from a seed
colour. See Color.Palette.Theme for the full algorithm and
option list.
Arguments
seedis anything accepted byColor.new/1.
Options
See Color.Palette.Theme.new/2.
Returns
- A
Color.Palette.Themestruct.
Examples
iex> theme = Color.Palette.theme("#3b82f6")
iex> match?(%Color.Palette.Theme{}, theme)
true
@spec tonal( Color.input(), keyword() ) :: Color.Palette.Tonal.t()
Generates a tonal scale — N shades of a single hue — from a seed
colour. See Color.Palette.Tonal for the full algorithm.
Arguments
seedis anything accepted byColor.new/1— a hex string, a CSS named colour, an%Color.SRGB{}struct, etc.
Options
:stopsis the list of stop labels to generate, default[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950](Tailwind's convention).:light_anchoris the Oklch lightness of the lightest stop, default0.98.:dark_anchoris the Oklch lightness of the darkest stop, default0.15.:hue_drift— whentrue, the hue drifts slightly toward yellow at the light end and toward blue at the dark end, matching how human vision perceives lightness. Defaultfalse.:gamutis the working space to gamut-map each stop into, default:SRGB. Widening the gamut (for example:P3_D65or:Rec2020) gives non-seed stops more chroma headroom and produces a smoother ramp for saturated seeds, at the cost of colours that may not display accurately on sRGB-only monitors.:chroma_ceilingis a float in(0.0, 1.0]that caps each stop's chroma atceiling × max_chroma(L, H, gamut). The default1.0lets stops hug the gamut boundary. Lowering it (for example0.85) produces a more muted, evenly saturated-looking ramp.:nameis an optional string label stored on the struct.
Returns
- A
Color.Palette.Tonalstruct.
Examples
iex> palette = Color.Palette.tonal("#3b82f6", name: "blue")
iex> palette.name
"blue"
iex> Map.keys(palette.stops) |> Enum.sort()
[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]