Color utilities
Convert colors, measure contrast, build palettes, and generate full themes from your own code, with the functions Chameleon builds its themes with. To theme your components from a seed color, start with Theming.
What you can build
- A color picker.
toneStrip,chromaStrip, andhueStripreturn the gradient stops for each slider.maxChromaAtreturns the highest chroma sRGB can hold at a hue and tone, so the chroma slider never offers a color a hex string can't express. - Accessible color pairs.
contrastRatioFromHexmeasures WCAG contrast andapcaContrastmeasures APCA (the Accessible Perceptual Contrast Algorithm).foregroundTonefinds the text tone that reaches a target WCAG ratio on a background, andsolveRolefinds one color that stays readable on every surface it can land on. - A palette from a photo.
seedColorFromImagepicks the best seed color from an image, andquantizeAndScorereturns several, ranked. - A theme from a brand color.
generateThemeCssFromHexwrites every--ct-sys-color-*custom property as a stylesheet that follows the user's light or dark setting, with fixed light and dark declarations alongside.
Hex, ARGB, and HCT
Besides CSS hex strings, functions take and return colors in two forms:
- ARGB packs a color into one 32-bit unsigned integer: alpha in the top byte, then red, green, and blue. Opaque purple is
0xff6750a4. Convert to and from a CSS hex string withargbFromHexandhexFromArgb. - HCT describes a color by hue, chroma, and tone, as an object
{ hue, chroma, tone }. Google created it for Material Design and explains it in The Science of Color & Design.
Work in HCT whenever you change one property of a color and need the others to stay put:
- Hue is the angle on the color wheel, from 0 to 360 degrees.
- Chroma is colorfulness, from 0 for a gray to about 120. The highest chroma available depends on hue and tone;
maxChromaAtreturns the maximum chroma for a specific hue and tone. - Tone is perceived lightness, from 0 for black to 100 for white. WCAG contrast between two colors depends on their tones alone, so choose tones for contrast first and hues second.
Build time or run time
The same functions, with the same names and signatures, ship in two packages. Which one you use depends on when you know your colors.
- At build time, when you know the themes before the app ships. Generate them in a Node or Deno build script with
@materia-technologies-inc/chameleon-build-utilities/color, which requires a license grant, and serve the result as a stylesheet. The page loads with its colors ready and does no color work in the browser. Prefer this, above all for the first theme your app shows. - At run time, when the theme depends on something the app learns while it runs, such as a color the user picks or an image they upload. Import the same functions from
@materia-technologies-inc/chameleon-components-web, the package that registers the components.
The build package doesn't have the Async functions, and the functions that decode an image don't work outside a browser.
Built on Material Color Utilities
Chameleon's color utilities extend Google's Material Color Utilities by adding:
- Complete theme stylesheets from a seed color.
generateThemeSheetwrites every--ct-sys-color-*role into a stylesheet you can serve as it is: it follows the user's light or dark setting, can be pinned to light or dark, steps up to higher contrast when the user asks for more, and works in browsers with or withoutlight-dark()support.generateThemeCssFromHexgives you one theme's CSS to place yourself. - Information, success, and warning color roles, alongside error.
- The Chord theme variant.
- APCA contrast, alongside WCAG contrast.
- The contrast solvers
solveRoleandsolveLadder. - Strip and batch functions that compute a run of colors in one call, such as
toneStrip,maxChromaStrip, andhctBatchFromArgbF32, for example to draw the gradients behind a color picker's sliders.
Using the color utilities
If your page already loads Chameleon, the color functions are already installed. Import them from the same package:
import { argbFromHex, hctFromArgb } from "@materia-technologies-inc/chameleon-components-web";
Calling a color function in the browser needs no license grant. The package itself needs a browser, because importing it registers every cw-* element; in a build script, use the build package (the In a build script tab).
Pick a readable text color
Keep a background's hue, and find the tone that reaches a WCAG contrast of 4.5:1 against it:
import {
argbFromHct,
argbFromHex,
foregroundTone,
hctFromArgb,
hexFromArgb,
} from "@materia-technologies-inc/chameleon-components-web";
const background = hctFromArgb(argbFromHex("#6750a4"));
const tone = foregroundTone(background.tone, 4.5);
const text = hexFromArgb(argbFromHct(background.hue, 16, tone));
// text is "#ded5ee", which measures 4.56:1 on #6750a4
Draw a tone slider
Give a slider track the full range of tones at a color's hue and chroma, in 11 stops:
import { argbFromHex, hctFromArgb, hexFromArgb, toneStrip } from "@materia-technologies-inc/chameleon-components-web";
const { hue, chroma } = hctFromArgb(argbFromHex("#6750a4"));
const stops = Array.from(toneStrip(hue, chroma, 0, 100, 11), hexFromArgb);
document.querySelector(".tone-track").style.background = `linear-gradient(to right, ${stops.join(", ")})`;
// stops run from "#000000" through "#6750a4" to "#ffffff"
Take a seed color from a photo
Pass a file straight from a file input; the browser decodes it:
import { hexFromArgb, seedColorFromImage } from "@materia-technologies-inc/chameleon-components-web";
const picker = document.createElement("input");
picker.type = "file";
picker.accept = "image/*";
picker.addEventListener("change", async () => {
const file = picker.files?.[0];
if (file) {
const seed = await seedColorFromImage(file);
console.log(hexFromArgb(seed));
}
});
document.body.append(picker);
Keep the page responsive
Eight heavy functions also come in an Async form, such as generateThemeSheetAsync and quantizePixelsAsync. Call that form while the user is interacting with the page. It takes the same arguments, returns a promise of the same result, and does its work on a worker. Where your Content Security Policy doesn't allow worker-src blob:, it runs on the page's thread and still returns the right result.
Use the build package to compute colors in a script that runs on Node 20 or newer, or on Deno, such as a script that writes your theme stylesheet before you deploy.
Install the packages
Install the build package and the grant tooling as development dependencies. The color functions won't run without the grant tooling.
npm install --save-dev @materia-technologies-inc/chameleon-build-utilities @materia-technologies-inc/chameleon-grant
License the script
Set the CHAMELEON_LICENSE_TOKEN environment variable to your build token, as every Chameleon build requires; License your build shows how. Importing the color functions obtains a license grant with that token. Without the token, the import throws before any of your code runs:
[chameleon-build-utilities] No grant is available: CHAMELEON_LICENSE_TOKEN is not set. Set it to your build token (it must reach a non-interactive shell).
- A script still runs offline for up to 15 days after its last connected run.
- A refused token stops the script, as it stops any Chameleon build.
Use the build package for build scripts that run, produce a result, and then exit, rather than for long-running servers.
Write a theme stylesheet
Save this as theme.mjs and run it with node theme.mjs to write a stylesheet that follows the user's light or dark setting:
import { writeFile } from "node:fs/promises";
import { generateThemeCssFromHex } from "@materia-technologies-inc/chameleon-build-utilities/color";
const theme = generateThemeCssFromHex("#6750a4");
await writeFile("theme.css", theme.system);
// theme.css sets every --ct-sys-color-* property on :root
theme.light and theme.dark are declaration blocks with no selector, for a region fixed to light or dark. Write one inside your own rule, such as [data-theme="dark"] { … }.
Convert and compare colors
Save this as palette.mjs and run it with node palette.mjs:
import {
argbFromHct,
argbFromHex,
contrastRatioFromHex,
hctFromArgb,
hexFromArgb,
} from "@materia-technologies-inc/chameleon-build-utilities/color";
// Hex to HCT
const { hue, chroma, tone } = hctFromArgb(argbFromHex("#6750a4"));
console.log(Math.round(hue), Math.round(chroma), Math.round(tone)); // 299 48 40
// The same hue and chroma at tone 90, back to hex
const light = hexFromArgb(argbFromHct(hue, chroma, 90));
console.log(light); // #e9ddff
// The WCAG contrast ratio between the two
console.log(contrastRatioFromHex("#6750a4", light).toFixed(2)); // 4.98
Take colors from an image
A script has no browser to decode an image, so seedColorFromImage, quantizeImage, quantizeAndScore, and pixelsFromImage throw there. Decode it with any image library that returns raw RGBA bytes, pack each pixel into an ARGB integer with argbFromRgb, and pass the array to seedColorFromPixels, quantizePixels, or quantizeAndScorePixels.
Conversion
Move a color between a CSS hex string, separate red, green, and blue channels, and an ARGB integer.
argbFromHex
Convert a CSS hex color string to an ARGB integer. Accepts 3, 4, 6, or 8 hex digit formats with or without leading '#'.
function argbFromHex(hex: string): number;
hex: CSS hex color string (for example, '#c0ffee', '#fff', 'c0ffee')- Returns: ARGB color as a 32-bit unsigned integer (alpha defaults to 0xFF for 3/6-digit input)
hexFromArgb
Convert an ARGB integer to a CSS hex color string.
function hexFromArgb(argb: number): string;
argb: ARGB color as a 32-bit unsigned integer- Returns: CSS hex color string (for example, '#c0ffee'): always lowercase, 6-digit, without alpha
argbFromRgb
Create an ARGB color from RGB components. Alpha is set to 255 (fully opaque).
function argbFromRgb(r: number, g: number, b: number): number;
r: Red component (0–255)g: Green component (0–255)b: Blue component (0–255)- Returns: ARGB color as a 32-bit unsigned integer
blendColors
foreground composited over background at alpha, as #rrggbb.
function blendColors(foreground: string, background: string, alpha: number): string;
HCT
Convert to and from hue, chroma, and tone, and find how much chroma sRGB can hold.
HctColor
A color as hue, chroma, and tone. Hue is 0–360 degrees, chroma is colorfulness, typically 0–120, and tone is perceptual lightness, 0–100.
interface HctColor {
hue: number;
chroma: number;
tone: number;
}
hctFromArgb
Convert an ARGB color to HCT components.
function hctFromArgb(argb: number): HctColor;
argb: ARGB color as a 32-bit unsigned integer- Returns: HCT color with hue (0–360), chroma (0-~120), tone (0–100)
argbFromHct
Convert HCT components to an ARGB color.
function argbFromHct(hue: number, chroma: number, tone: number): number;
hue: Hue in degrees (0–360)chroma: Chroma (colorfulness, typically 0–120)tone: Tone (lightness L*, 0–100)- Returns: ARGB color as a 32-bit unsigned integer
solveHct
A color from hue, chroma and tone, reporting what was actually achieved.
function solveHct(hue: number, chroma: number, tone: number): {
argb: number;
hue: number;
chroma: number;
tone: number;
};
hue: Hue in degrees (0–360)chroma: Requested chromatone: Tone (0–100)- Returns: The packed color, and the hue, chroma and tone sRGB actually held
colorsAt
Gamut-clamped colors from HCT, reporting what was achieved, in one call.
function colorsAt(requests: readonly ColorRequest[]): readonly SolvedColor[];
requests: The colors wanted.- Returns: One entry per request, in order.
ColorRequest
A color asked for in HCT, for colorsAt.
interface ColorRequest {
hue: number;
chroma: number;
tone: number;
}
SolvedColor
A color, and what sRGB actually held.
interface SolvedColor {
argb: number;
hue: number;
chroma: number;
tone: number;
}
hctBatchFromArgbF32
Convert an array of ARGB colors to HCT components (f32 output) in a single call. Returns a flat Float32Array: [hue0, chroma0, tone0, ...]
function hctBatchFromArgbF32(argbs: Uint32Array | number[], out?: Float32Array): Float32Array;
argbBatchFromHct
Convert arrays of HCT components to ARGB colors in a single call.
function argbBatchFromHct(hues: Float64Array | Float32Array | number[], chromas: Float64Array | Float32Array | number[], tones: Float64Array | Float32Array | number[], out?: Uint32Array): Uint32Array;
maxChromaAt
The highest chroma sRGB actually holds at this hue and tone.
function maxChromaAt(hue: number, tone: number): number;
hue: Hue in degrees (0–360)tone: Tone (0–100)- Returns: Chroma at the gamut boundary; 0 at tone 0 and tone 100.
maxChromaStrip
The gamut boundary across a range of tones, in one call.
function maxChromaStrip(hue: number, startTone: number, endTone: number, steps: number, out?: Float64Array): Float64Array;
hue: Hue in degrees (0–360)startTone: Starting tone (0–100)endTone: Ending tone (0–100)steps: Number of samples, minimum 2out: Optional output buffer to write into instead of allocating- Returns: Chroma at the boundary for each tone
peakChromaRamp
The tone carrying the most chroma, for each of several hues, in one call.
function peakChromaRamp(hues: readonly number[], startTone: number, toneStep: number, steps: number, chromaCeiling: number): readonly PeakChroma[];
hues: Hues in degrees. Used verbatim.startTone: First tone of the search grid.toneStep: Distance between grid tones.steps: How many grid tones.chromaCeiling: Upper bound on the chroma taken from any tone.- Returns: One entry per hue, in order.
PeakChroma
The most-chroma tone found at one hue, and the color there.
interface PeakChroma {
argb: number;
tone: number;
chroma: number;
hue: number;
achievedChroma: number;
achievedTone: number;
}
argb: The color.tone: The grid tone that won.chroma:min(ceiling, boundary at that tone).hue: Hue, as sRGB actually held it.achievedChroma: Chroma, as sRGB actually held it.achievedTone: Tone, as sRGB actually held it.
Contrast
Measure contrast between colors or tones, and solve for a color that reaches a contrast you state.
contrastRatio
The WCAG contrast ratio between two tones, 1.0 to 21.0.
function contrastRatio(toneA: number, toneB: number): number;
toneA: First tone (0–100)toneB: Second tone (0–100)
contrastRatioFromHex
WCAG 2.x contrast between two hex colors, 1 to 21.
function contrastRatioFromHex(a: string, b: string): number;
wcagRatioArgb
WCAG 2.x contrast between two ARGB colors, measured from their 8-bit sRGB channels, 1 to 21.
function wcagRatioArgb(a: number, b: number): number;
apcaContrast
APCA Lc, W3C draft 0.1.9, as an absolute value.
function apcaContrast(text: string, background: string): number;
apcaLcArgb
APCA Lc, W3C draft 0.1.9, between two ARGB colors, as an absolute value.
function apcaLcArgb(text: number, background: number): number;
relativeLuminance
WCAG 2.x relative luminance, 0 to 1.
function relativeLuminance(hex: string): number;
toneFromLuminance
The tone whose luminance is y: the inverse of relativeLuminance up to the perceptual transfer, that is, CIE Y to CIE L*.
function toneFromLuminance(y: number): number;
lighterTone
The tone above tone that reaches ratio against it.
function lighterTone(tone: number, ratio: number): number;
tone: The tone to contrast against (0–100)ratio: Target contrast ratio (1.0–21.0)- Returns: The tone, or -1 if no lighter tone reaches the ratio
darkerTone
The tone below tone that reaches ratio against it.
function darkerTone(tone: number, ratio: number): number;
tone: The tone to contrast against (0–100)ratio: Target contrast ratio (1.0–21.0)- Returns: The tone, or -1 if no darker tone reaches the ratio
foregroundTone
A foreground tone that reaches ratio against a background tone.
function foregroundTone(backgroundTone: number, ratio: number): number;
backgroundTone: The background tone (0–100)ratio: Target contrast ratio (1.0–21.0)
solveRole
The first tone along a walk, at this hue and chroma, that clears every contrast floor against every surface, solved and audited in one call.
function solveRole(request: RoleRequest): RoleSolution;
request: The color asked for, the surfaces, the floors and the walk.- Returns: The color, how it was arrived at, and what it achieved.
RoleRequest
Everything solveRole needs.
interface RoleRequest {
hue: number;
chroma: number;
surfaces: readonly number[];
floors: readonly ContrastFloor[];
from: number;
to: number;
step: number;
boundSlack: number;
fallbackMode: RoleFallbackModeValue;
fallbackArgb?: number;
}
hue: Hue in degrees. Used verbatim.chroma: Chroma asked for. Clamped to the sRGB boundary at each candidate tone.surfaces: Every surface the role may be drawn on. Every floor must clear on each.floors: The minima to clear, in any mix of readings.from: Where the design would like the color to sit.to: The far end of the tone window, inclusive.step: Signed tone step. Negative walks down toward dark, positive walks up.boundSlack: Tones to back the WCAG start bound off by, towardfrom.fallbackMode: What to return when nothing clears.fallbackArgb: The color forRoleFallbackMode.Fixed. Ignored otherwise.
RoleSolution
A solved role, with what it cost and what it achieved.
interface RoleSolution {
argb: number;
tone: number;
hue: number;
chroma: number;
achievedTone: number;
status: RoleStatusValue;
candidates: number;
floorMinima: readonly number[];
}
argb: The color.tone: The walk tone that won. For a fixed fallback this isachievedTone.hue: Hue, as sRGB actually held it.chroma: Chroma, as sRGB actually held it.achievedTone: Tone, as sRGB actually held it.status: How the answer was arrived at.candidates: How many tones were evaluated.floorMinima: The WORST reading the returned color achieved over all surfaces, one per floor, in the order the floors were given and each in its own metric.
ContrastFloor
A minimum a candidate color must clear, in one reading.
interface ContrastFloor {
metric: ContrastMetricValue;
minimum: number;
}
solveLadder
A ladder of surfaces matched on contrast STEPS rather than on tones.
function solveLadder(request: LadderRequest): readonly LadderStep[];
request: The ground, the grid, and the rungs.- Returns: One entry per rung, in order.
LadderRequest
Everything solveLadder needs.
interface LadderRequest {
ground: number;
gridFirst: number;
gridStep: number;
gridCount: number;
window: number;
rungs: readonly LadderRung[];
}
ground: Output index 0. Not itself solved; rungs step off it.gridFirst: Tone at grid index 0.gridStep: Distance between adjacent grid tones.gridCount: How many grid tones there are.window: Grid indices either side of each root to evaluate.rungs: What to solve, in order.
LadderRung
One rung of a matched ladder: a color to solve, its parent, and the step.
interface LadderRung {
hue: number;
chroma: number;
parent: number;
ratio: number;
}
hue: Hue of the rung. Used verbatim.chroma: Chroma asked for. Clamped to the sRGB boundary at each candidate tone.parent: Index into the OUTPUT ladder: 0 is the ground,k >= 1is rungk - 1. Must name a rung already solved, so the shape is a tree: several rungs may hang off the ground.ratio: The WCAG contrast step to reproduce against that parent.
LadderStep
One solved rung.
interface LadderStep {
argb: number;
tone: number;
hue: number;
chroma: number;
achievedTone: number;
ratio: number;
}
argb: The color.tone: The grid tone it sits on.hue: Hue, as sRGB actually held it.chroma: Chroma, as sRGB actually held it.achievedTone: Tone, as sRGB actually held it.ratio: The contrast actually achieved against the parent.
Tones and palettes
Build strips of colors for sliders and swatches, and Material's standard tonal palette.
toneStrip
Generates a gradient of ARGB colors varying tone at a fixed hue and chroma. Made for tone sliders in color pickers.
function toneStrip(hue: number, chroma: number, startTone: number, endTone: number, steps: number): Uint32Array;
hue: Hue in degrees (0–360)chroma: Chroma (colorfulness)startTone: Starting tone (0–100)endTone: Ending tone (0–100)steps: Number of gradient stops (for example, 101 for 0–100 inclusive)- Returns: Array of ARGB colors
chromaStrip
Generates a gradient of ARGB colors varying chroma at a fixed hue and tone. Made for chroma sliders.
function chromaStrip(hue: number, tone: number, startChroma: number, endChroma: number, steps: number): Uint32Array;
hue: Hue in degrees (0–360)tone: Tone (0–100)startChroma: Starting chroma (typically 0)endChroma: Ending chroma (typically 100–150)steps: Number of gradient stops- Returns: Array of ARGB colors
hueStrip
Generates a gradient of ARGB colors varying hue at a fixed chroma and tone. Made for hue sliders covering the full 360 degrees.
function hueStrip(chroma: number, tone: number, steps: number): Uint32Array;
chroma: Chroma (colorfulness)tone: Tone (0–100)steps: Number of hue stops (for example, 360 for 1° resolution)- Returns: Array of ARGB colors covering hue 0° to 360°
tonalPaletteMaterialTones
Material Design 3 standard tonal palette tones. Returns 13 ARGB colors at tones: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100
function tonalPaletteMaterialTones(hue: number, chroma: number): Uint32Array;
hue: Hue angle (0–360)chroma: Chroma value- Returns: Array of 13 ARGB colors at Material Design standard tones
Schemes and themes
Generate color schemes and theme stylesheets from a seed color. For when to use each and what the options do, see the Color tab of Theming.
generateThemeSheet
Generates one stylesheet carrying every theme, following the OS scheme and overridable per subtree. Given two contrast levels, it emits the higher one under prefers-contrast: more; a stylesheet holds at most two.
function generateThemeSheet(themes: readonly ThemeSpec[], options?: ThemeSheetOptions): string;
themes: The themes to emit, in cascade order.options: Sheet-wide defaults and emission control.- Returns: The complete stylesheet.
generateThemeSheetAsync
generateThemeSheet, off the main thread. The heaviest call here: a stylesheet for every theme given, at the contrast levels given, up to two.
In the browser package only.
function generateThemeSheetAsync(themes: readonly ThemeSpec[], options?: ThemeSheetOptions): Promise<string>;
themes: The themes to emit, in cascade order.options: Sheet-wide defaults and emission control.
ThemeSpec
One theme in a generateThemeSheet call.
interface ThemeSpec {
readonly selector: string | readonly string[];
readonly seed: string | number | HctColor;
readonly variant?: ColorThemeVariantType;
readonly specification?: ColorSpecificationType;
readonly contrasts?: readonly ContrastValue[];
readonly declarations?: Readonly<Record<string, string>>;
readonly overrides?: Readonly<Record<string, SchemeOverride>>;
}
selector: The selector(s) this theme's tokens are declared on: a full CSS selector or a list of them, never a bare class name.seed: The source color: a CSS hex string, an ARGB integer, or an HCT triple.variant: Theme variant. Defaults to the sheet-widevariant.specification: Color specification. Defaults to the sheet-widespecification.contrasts: Contrast levels this theme is generated at. Defaults to the sheet-widecontrasts.declarations: Declarations emitted with this theme that are not colors: corner shapes, radius scales, typefaces.overrides: Per-token color overrides, applied to the generated token maps before any block is assembled.
ThemeSheetOptions
Sheet-wide options for generateThemeSheet.
interface ThemeSheetOptions {
readonly variant?: ColorThemeVariantType;
readonly specification?: ColorSpecificationType;
readonly contrasts?: readonly ContrastValue[];
readonly compat?: boolean;
readonly colorSchemeSelector?: string | null;
readonly forcing?: {
readonly light: string;
readonly dark: string;
} | null;
readonly layer?: string;
readonly onWarning?: (message: string) => void;
}
variant: Default variant for themes that do not set one.specification: Default specification for themes that do not set one.contrasts: Default contrast levels for themes that do not set their own.compat: Whentrue(the default), emit the plain-hex baseline andprefers-color-schemeblock alongside thelight-dark()upgrade, so a browser withoutlight-dark()still gets a fully themed page.colorSchemeSelector: The selectorcolor-scheme: light darkis declared on. Defaults to:root.forcing: Class names for the two scheme-forcing rules, ornullto emit neither. Defaults to.theme-lightand.theme-dark.layer: Wrap the whole sheet in@layer <name>.onWarning: Where a contrast warning goes. Defaults toconsole.warn.
SchemeOverride
An override of one color token: the same value in both schemes, or one scheme pinned and the other left generated.
type SchemeOverride = string | {
readonly light?: string;
readonly dark?: string;
};
generateThemeCssFromHex
Generates theme CSS (light, dark and system-following) from a hex seed color.
function generateThemeCssFromHex(hexColor: string, request?: ThemeCssRequest): ColorThemeCss;
hexColor: The base color for the theme as a CSS compliant hex color (for example, #c0ffee).request: Variant, specification, contrast, selector and compat, all optional.- Returns: The generated theme CSS (see
ColorThemeCss).
generateThemeCssFromHexAsync
generateThemeCssFromHex, off the main thread. Light, dark and system-following CSS from a hex seed.
In the browser package only.
function generateThemeCssFromHexAsync(hexColor: string, request?: ThemeCssRequest): Promise<ColorThemeCss>;
hexColor: The base color for the theme as a CSS compliant hex color (for example, #c0ffee).request: Variant, specification, contrast, selector and compat, all optional.
generateThemeCssFromHct
Generates theme CSS (light, dark and system-following) from an HCT seed color.
function generateThemeCssFromHct(hctColor: HctColor, request?: ThemeCssRequest): ColorThemeCss;
hctColor: The base color for the theme as an HCT color ({hue, chroma, tone}).request: Variant, specification, contrast, selector and compat, all optional.- Returns: The generated theme CSS (see
ColorThemeCss).
generateThemeCssFromArgb
Generates theme CSS (light, dark and system-following) from an ARGB seed color.
function generateThemeCssFromArgb(argb: number, request?: ThemeCssRequest): ColorThemeCss;
argb: The base color as an ARGB integer.request: Variant, specification, contrast, selector and compat, all optional.- Returns: The generated theme CSS (see
ColorThemeCss).
generateThemeCssFromArgbAsync
generateThemeCssFromArgb, off the main thread. The same as the hex form, from a packed ARGB integer.
In the browser package only.
function generateThemeCssFromArgbAsync(argb: number, request?: ThemeCssRequest): Promise<ColorThemeCss>;
argb: The base color as an ARGB integer.request: Variant, specification, contrast, selector and compat, all optional.
ThemeCssRequest
Request form for generateThemeCssFromHex, FromHct and FromArgb.
interface ThemeCssRequest {
readonly variant?: ColorThemeVariantType;
readonly spec?: ColorSpecificationType;
readonly contrast?: number;
readonly selector?: string;
readonly compat?: boolean;
}
variant: Theme variant. Defaults toColorThemeVariant.Expressive.spec: Color specification. Defaults toColorSpecification.MaterialExpressive2025.contrast: Contrast level (-1 to 1; standard values: 0, 0.5, 1). Defaults to 0.selector: Selector thesystemstylesheet targets. Defaults to:root.compat: Whentrue(the default),systemincludes theprefers-color-schemebaseline so browsers withoutlight-dark()still get a fully themed light/dark experience. Set tofalsefor evergreen-only targets to emit just the leanlight-dark()rule.
ColorThemeCss
CSS for a generated color theme in its three delivery modes.
interface ColorThemeCss {
readonly light: string;
readonly dark: string;
readonly system: string;
}
light: Declarations forcing the light theme (color-scheme: light+ hex values).dark: Declarations forcing the dark theme (color-scheme: dark+ hex values).system: A complete stylesheet following the system light/dark setting, targetingoptions.selector(default:root). Unlikelight/darkthis is not a bare declaration block, because the compatibility structure requires@media/@supportsat-rules, which cannot nest inside a declaration list.
ColorThemeCssOptions
Options for generateThemeCssFromArgb and friends.
interface ColorThemeCssOptions {
readonly selector?: string;
readonly compat?: boolean;
}
selector: Selector thesystemstylesheet targets. Defaults to:root.compat: Whentrue(the default),systemincludes theprefers-color-schemebaseline so browsers withoutlight-dark()still get a fully themed light/dark experience. Set tofalsefor evergreen-only targets to emit just the leanlight-dark()rule.
generateDynamicThemeFromHex
Generates a dynamic theme based on the provided hex color and theme variant.
function generateDynamicThemeFromHex(hexColor: string, request?: DynamicThemeRequest): ColorThemeSet;
hexColor: The base color for the theme as a CSS compliant hex color (for example, #c0ffee).request: Variant and specification, both optional.- Returns: The generated theme.
generateDynamicThemeFromHct
Generates a dynamic theme based on the provided HCT color and theme variant.
function generateDynamicThemeFromHct(hctColor: HctColor, request?: DynamicThemeRequest): ColorThemeSet;
hctColor: The base color for the theme as an HCT color ({hue, chroma, tone}).request: Variant and specification, both optional.- Returns: The generated theme.
generateDynamicThemeFromArgb
Generates a dynamic theme based on the provided ARGB color and theme variant.
function generateDynamicThemeFromArgb(argb: number, request?: DynamicThemeRequest): ColorThemeSet;
argb: The base color as an ARGB integer.request: Variant and specification, both optional.- Returns: The generated theme.
generateDynamicThemeTokensFromHex
Generates tokens for a dynamic theme based on the provided hex color and theme variant.
function generateDynamicThemeTokensFromHex(hexColor: string, request?: DynamicThemeRequest): Map<ColorThemeName, string>;
hexColor: The base color for the theme as a CSS compliant hex color (for example, #c0ffee).request: Variant and specification, both optional.- Returns: Map of theme name (for example,
lightDefault) to its CSS declarations string.
generateDynamicThemeTokensFromHct
Generates tokens for a dynamic theme based on the provided HCT color and theme variant.
function generateDynamicThemeTokensFromHct(hctColor: HctColor, request?: DynamicThemeRequest): Map<ColorThemeName, string>;
hctColor: The base color for the theme as an HCT color ({hue, chroma, tone}).request: Variant and specification, both optional.- Returns: Map of theme name (for example,
lightDefault) to its CSS declarations string.
DynamicThemeRequest
Request form for generateDynamicThemeFromHex, FromHct, FromArgb and the generateDynamicThemeTokens* siblings.
interface DynamicThemeRequest {
readonly variant?: ColorThemeVariantType;
readonly spec?: ColorSpecificationType;
}
variant: Theme variant. Defaults toColorThemeVariant.Expressive.spec: Color specification. Defaults toColorSpecification.MaterialExpressive2025.
ColorThemeSet
Represents a set of light and dark color themes, each in default, medium and high contrast. See Material Design Color System for more details.
interface ColorThemeSet {
readonly lightDefault: ColorTokens;
readonly lightMedium: ColorTokens;
readonly lightHigh: ColorTokens;
readonly darkDefault: ColorTokens;
readonly darkMedium: ColorTokens;
readonly darkHigh: ColorTokens;
}
lightDefault: The default light theme.lightMedium: The medium contrast light theme.lightHigh: The high contrast light theme.darkDefault: The default dark theme.darkMedium: The medium contrast dark theme.darkHigh: The high contrast dark theme.
ColorThemeName
Name of one of the six themes in a ColorThemeSet.
type ColorThemeName = keyof ColorThemeSet;
ColorTokens
Represents a set of color tokens for a specific theme.
interface ColorTokens extends Map<string, string> {
}
createSchemeSet
Creates color schemes for one or more contrast levels, returning light and dark for each. It returns ARGB numbers only: to produce a stylesheet, use generateThemeCssFromHex or generateThemeSheet.
function createSchemeSet(argb: number, variant: ColorThemeVariantType, contrasts: number[], spec?: ColorSpecificationType): SchemeSetResult;
argb: Source color as ARGBvariant: Color theme variant (use ColorThemeVariant enum)contrasts: Array of contrast levels (each -1 to 1; standard values: 0, 0.5, 1)spec: Color specification (use ColorSpecification enum, defaults to MaterialExpressive2025). Read byExpressive,Neutral,TonalSpotandVibrant; discarded by the other six variants, which always resolve toMaterialYou2021.- Returns: SchemeSetResult with
schemes[i].light/schemes[i].darkfor each contrast
createSchemeSetAsync
createSchemeSet, off the main thread. Every scheme for one seed at the contrast levels given.
In the browser package only.
function createSchemeSetAsync(argb: number, variant: ColorThemeVariantType, contrasts: number[], spec?: ColorSpecificationType): Promise<SchemeSetResult>;
argb: Source color as ARGBvariant: Color theme variant (use ColorThemeVariant enum)contrasts: Array of contrast levels (each -1 to 1; standard values: 0, 0.5, 1)spec: Color specification (use ColorSpecification enum, defaults to MaterialExpressive2025). Read byExpressive,Neutral,TonalSpotandVibrant; discarded by the other six variants, which always resolve toMaterialYou2021.
createScheme
Creates a single color scheme, light or dark, at one contrast level.
function createScheme(argb: number, variant: ColorThemeVariantType, isDark: boolean, contrast: number, spec?: ColorSpecificationType): SchemeColors;
argb: Source color as ARGBvariant: Color theme variant (use ColorThemeVariant enum)isDark:truefor dark scheme,falsefor lightcontrast: Contrast level (-1 to 1; standard values: 0, 0.5, 1)spec: Color specification (use ColorSpecification enum, defaults to MaterialExpressive2025). Read byExpressive,Neutral,TonalSpotandVibrantonly;Chord,Content,Fidelity,FruitSalad,MonochromeandRainbowdiscard it and always resolve toMaterialYou2021. SeecreateSchemeSetfor why.- Returns: A single SchemeColors object with all Material 3 color roles
SchemeSetResult
Result of scheme set generation. Contains one pair of light/dark schemes per requested contrast level.
interface SchemeSetResult {
schemes: Array<{
light: SchemeColors;
dark: SchemeColors;
}>;
}
schemes: One entry per requested contrast level, in the same order as the contrasts array.
SchemeColors
All Material Design color roles including severity (information, success, warning). Returned by createSchemeSet(): each scheme contains all roles. Property names are CSS custom property names (kebab-case with --ct-sys-color- prefix).
interface SchemeColors {
"--ct-sys-color-background": number;
"--ct-sys-color-error": number;
"--ct-sys-color-error-container": number;
"--ct-sys-color-error-dim": number;
"--ct-sys-color-information": number;
"--ct-sys-color-information-container": number;
"--ct-sys-color-inverse-on-surface": number;
"--ct-sys-color-inverse-primary": number;
"--ct-sys-color-inverse-surface": number;
"--ct-sys-color-neutral-palette-key-color": number;
"--ct-sys-color-neutral-variant-palette-key-color": number;
"--ct-sys-color-on-background": number;
"--ct-sys-color-on-error": number;
"--ct-sys-color-on-error-container": number;
"--ct-sys-color-on-information": number;
"--ct-sys-color-on-information-container": number;
"--ct-sys-color-on-primary": number;
"--ct-sys-color-on-primary-container": number;
"--ct-sys-color-on-primary-fixed": number;
"--ct-sys-color-on-primary-fixed-variant": number;
"--ct-sys-color-on-secondary": number;
"--ct-sys-color-on-secondary-container": number;
"--ct-sys-color-on-secondary-fixed": number;
"--ct-sys-color-on-secondary-fixed-variant": number;
"--ct-sys-color-on-success": number;
"--ct-sys-color-on-success-container": number;
"--ct-sys-color-on-surface": number;
"--ct-sys-color-on-surface-variant": number;
"--ct-sys-color-on-tertiary": number;
"--ct-sys-color-on-tertiary-container": number;
"--ct-sys-color-on-tertiary-fixed": number;
"--ct-sys-color-on-tertiary-fixed-variant": number;
"--ct-sys-color-on-warning": number;
"--ct-sys-color-on-warning-container": number;
"--ct-sys-color-outline": number;
"--ct-sys-color-outline-variant": number;
"--ct-sys-color-primary": number;
"--ct-sys-color-primary-container": number;
"--ct-sys-color-primary-dim": number;
"--ct-sys-color-primary-fixed": number;
"--ct-sys-color-primary-fixed-dim": number;
"--ct-sys-color-primary-palette-key-color": number;
"--ct-sys-color-scrim": number;
"--ct-sys-color-secondary": number;
"--ct-sys-color-secondary-container": number;
"--ct-sys-color-secondary-dim": number;
"--ct-sys-color-secondary-fixed": number;
"--ct-sys-color-secondary-fixed-dim": number;
"--ct-sys-color-secondary-palette-key-color": number;
"--ct-sys-color-shadow": number;
"--ct-sys-color-success": number;
"--ct-sys-color-success-container": number;
"--ct-sys-color-surface": number;
"--ct-sys-color-surface-bright": number;
"--ct-sys-color-surface-container": number;
"--ct-sys-color-surface-container-high": number;
"--ct-sys-color-surface-container-highest": number;
"--ct-sys-color-surface-container-low": number;
"--ct-sys-color-surface-container-lowest": number;
"--ct-sys-color-surface-dim": number;
"--ct-sys-color-surface-tint": number;
"--ct-sys-color-surface-variant": number;
"--ct-sys-color-tertiary": number;
"--ct-sys-color-tertiary-container": number;
"--ct-sys-color-tertiary-dim": number;
"--ct-sys-color-tertiary-fixed": number;
"--ct-sys-color-tertiary-fixed-dim": number;
"--ct-sys-color-tertiary-palette-key-color": number;
"--ct-sys-color-warning": number;
"--ct-sys-color-warning-container": number;
}
SCHEME_ROLE_COUNT
The number of color roles in one scheme.
const SCHEME_ROLE_COUNT = 70;
schemeColorsFromArray
Converts a flat array of ARGB values, 70 per scheme, to a SchemeColors object.
function schemeColorsFromArray(data: Uint32Array, offset?: number): SchemeColors;
variantHonorsColorSpecification
Whether variant reads the ColorSpecification a caller passes to createScheme / createSchemeSet, or silently discards it and resolves to MaterialYou2021 regardless of what was passed.
function variantHonorsColorSpecification(variant: ColorThemeVariantType): boolean;
Image quantization
Reduce an image to its representative colors, and pick the best seed colors from them.
seedColorFromImage
Get the single best theme seed color from an image.
function seedColorFromImage(image: ImageSource, options?: ImageDecodeOptions): Promise<number>;
image: Image to process (seeImageSource)options: Decode options (ImageDecodeOptions)- Returns: Best ARGB color for theme generation
seedColorFromPixels
Get the single best theme seed color from raw ARGB pixels.
function seedColorFromPixels(pixels: Uint32Array | number[]): number;
pixels: Array of ARGB colors from the image- Returns: Best ARGB color for theme generation
seedColorFromPixelsAsync
seedColorFromPixels, off the main thread. Quantizes, scores and returns the one best seed.
In the browser package only.
function seedColorFromPixelsAsync(pixels: Uint32Array | number[]): Promise<number>;
pixels: Array of ARGB colors from the image
quantizeAndScore
Quantize and score an image in one call to get the best theme seed colors.
function quantizeAndScore(image: ImageSource, maxColors?: number, desiredCount?: number, options?: ImageDecodeOptions): Promise<Uint32Array>;
image: Image to process (seeImageSource)maxColors: Maximum colors to use in quantization (typically 128)desiredCount: Number of seed colors to return (typically 1–4)options: Decode options (ImageDecodeOptions)- Returns: Array of ARGB colors, best for theming first
quantizeAndScorePixels
Quantize and score raw ARGB pixels in one call to get the best theme seed colors.
function quantizeAndScorePixels(pixels: Uint32Array | number[], maxColors?: number, desiredCount?: number): Uint32Array;
pixels: Array of ARGB colors from the imagemaxColors: Maximum colors to use in quantization (typically 128)desiredCount: Number of seed colors to return (typically 1–4)- Returns: Array of ARGB colors, best for theming first
quantizeAndScorePixelsAsync
quantizeAndScorePixels, off the main thread. Prefer it to quantizing and scoring separately.
In the browser package only.
function quantizeAndScorePixelsAsync(pixels: Uint32Array | number[], maxColors?: number, desiredCount?: number): Promise<Uint32Array>;
pixels: Array of ARGB colors from the imagemaxColors: Maximum colors to use in quantization (typically 128)desiredCount: Number of seed colors to return (typically 1–4)
quantizeImage
Quantize an image to extract representative colors.
function quantizeImage(image: ImageSource, maxColors?: number, options?: ImageDecodeOptions): Promise<QuantizationResult>;
image: Image to quantize (seeImageSource)maxColors: Maximum number of colors to return (typically 128)options: Decode options (ImageDecodeOptions)- Returns: Quantized colors with their pixel populations, most common first
quantizePixels
Quantize raw ARGB pixels to extract representative colors.
function quantizePixels(pixels: Uint32Array | number[], maxColors?: number): QuantizationResult;
pixels: Array of ARGB colors from the image (one per pixel). Can be Uint32Array or number[]. Transparent pixels (alpha < 255) are ignored.maxColors: Maximum number of colors to return (typically 128)- Returns: Quantized colors with their pixel populations, most common first
quantizePixelsAsync
quantizePixels, off the main thread. Tens of milliseconds on a photograph, and the call most worth moving.
In the browser package only.
function quantizePixelsAsync(pixels: Uint32Array | number[], maxColors?: number): Promise<QuantizationResult>;
pixels: Array of ARGB colors from the image (one per pixel). Can be Uint32Array or number[]. Transparent pixels (alpha < 255) are ignored.maxColors: Maximum number of colors to return (typically 128)
scoreColors
Score quantized colors for their suitability as theme seed colors.
function scoreColors(colors: Uint32Array | number[], populations: Uint32Array | number[], desiredCount?: number): Uint32Array;
colors: Array of ARGB colors (for example,quantizeImage(...).colors)populations: Pixel count per color (for example,quantizeImage(...).populations)desiredCount: Number of colors to return (typically 4)- Returns: Array of top ARGB colors, best first
scoreColorsAsync
scoreColors, off the main thread. Ranks quantized colors by how well they seed a theme.
In the browser package only.
function scoreColorsAsync(colors: Uint32Array | number[], populations: Uint32Array | number[], desiredCount?: number): Promise<Uint32Array>;
colors: Array of ARGB colors (for example,quantizeImage(...).colors)populations: Pixel count per color (for example,quantizeImage(...).populations)desiredCount: Number of colors to return (typically 4)
pixelsFromImage
Decode an image and return its pixels as an ARGB array ready for quantization.
function pixelsFromImage(image: ImageSource, options?: ImageDecodeOptions): Promise<Uint32Array>;
image: Image to decode (seeImageSource)options: Decode options (ImageDecodeOptions)- Returns: Uint32Array of ARGB pixels
imageDataToArgb
Convert ImageData from a Canvas to an ARGB pixel array.
function imageDataToArgb(imageData: ImageData): Uint32Array;
imageData: ImageData from CanvasRenderingContext2D.getImageData()- Returns: Uint32Array of ARGB pixels
ImageSource
An image in any form the browser can decode or draw.
type ImageSource = Blob | ArrayBuffer | ArrayBufferView | ImageBitmap | ImageData | HTMLImageElement | HTMLCanvasElement | OffscreenCanvas;
ImageDecodeOptions
Options for decoding an ImageSource before quantization.
interface ImageDecodeOptions {
maxDimension?: number;
}
maxDimension: Longest edge (in pixels) the image is downscaled to before quantization. Quantization quality is insensitive to resolution, so downscaling large photos dramatically reduces work with no visible effect on the resulting theme. Set to 0 to disable downscaling. Default: 128.
QuantizationResult
Result of image quantization containing colors and their populations.
interface QuantizationResult {
colors: Uint32Array;
populations: Uint32Array;
}
colors: Array of quantized ARGB colors, sorted by population (most common first)populations: Pixel count for each color (parallel tocolors)
Enums and constants
Named values the functions above take and return.
ColorThemeVariant
Color theme variants: how a scheme is derived from its seed color.
const ColorThemeVariant: {
readonly Chord: "chord";
readonly Content: "content";
readonly Expressive: "expressive";
readonly Fidelity: "fidelity";
readonly FruitSalad: "fruit-salad";
readonly Monochrome: "monochrome";
readonly Neutral: "neutral";
readonly Rainbow: "rainbow";
readonly TonalSpot: "tonal-spot";
readonly Vibrant: "vibrant";
};
Chord: Three peer hues: the seed, plus 80 and 160 degrees around the color wheel. Each is carried at the same fraction of its own gamut as the seed, so a muted seed yields a muted scheme.Content: A theme that places the source color in theprimaryContainertoken.Expressive: A theme that is intentionally detached from the source color.Fidelity: A theme that places the source color in theprimaryContainertoken.FruitSalad: A playful theme: the source color's hue does not appear in the scheme.Monochrome: A grayscale theme.Neutral: A near grayscale color theme.Rainbow: A playful theme: the source color's hue does not appear in the theme.TonalSpot: A theme with low to medium colorfulness and a Tertiary TonalPalette with a hue related to the source color.Vibrant: A theme that maxes out colorfulness at each position in the Primary Tonal Palette.
ColorThemeVariantType
Type for variables that hold a ColorThemeVariant value.
type ColorThemeVariantType = (typeof ColorThemeVariant)[keyof typeof ColorThemeVariant];
ColorSpecification
Material color scheme specifications.
const ColorSpecification: {
readonly MaterialYou2021: "material-you-2021";
readonly MaterialExpressive2025: "material-expressive-2025";
};
MaterialYou2021: The 2021 Material You specification.MaterialExpressive2025: The 2025 Material Expressive specification.
ColorSpecificationType
Type for variables that hold a ColorSpecification value.
type ColorSpecificationType = (typeof ColorSpecification)[keyof typeof ColorSpecification];
ContrastLevel
Contrast levels Material specifies.
const ContrastLevel: {
readonly Standard: 0;
readonly Medium: 0.5;
readonly High: 1;
};
Standard: The contrast the schemes are designed at.Medium: Halfway between standard and high.High: Material's high-contrast scheme.
ContrastLevelType
Type for variables that hold a ContrastLevel value.
type ContrastLevelType = (typeof ContrastLevel)[keyof typeof ContrastLevel];
ContrastRange
The inclusive bounds of a contrast value. Beyond them, the contrast stops changing.
const ContrastRange: {
readonly Minimum: -1;
readonly Maximum: 1;
};
Minimum: Lower bound. Below this the curve returns itslowanchor unchanged.Maximum: Upper bound. Above this the curve returns itshighanchor unchanged.
ContrastValue
A contrast level: a named ContrastLevel member, or any scalar within ContrastRange.
type ContrastValue = ContrastLevelType | number;
ContrastMetric
Which reading a ContrastFloor is stated in.
const ContrastMetric: {
readonly Wcag21: 0;
readonly ApcaW3cDraft019: 1;
};
Wcag21: WCAG 2.x ratio over 8-bit sRGB channels, 1 to 21. Symmetric.ApcaW3cDraft019: APCA Lc, W3C draft 0.1.9, 0 to ~106. Text first, then background.
ContrastMetricValue
Type for variables that hold a ContrastMetric value.
type ContrastMetricValue = (typeof ContrastMetric)[keyof typeof ContrastMetric];
RoleFallbackMode
What solveRole returns when the walk clears nothing.
const RoleFallbackMode: {
readonly Fixed: 0;
readonly BestWcagSoFar: 1;
};
Fixed: Return the stated color.BestWcagSoFar: Return the evaluated candidate with the highest minimum WCAG ratio.
RoleFallbackModeValue
Type for variables that hold a RoleFallbackMode value.
type RoleFallbackModeValue = (typeof RoleFallbackMode)[keyof typeof RoleFallbackMode];
RoleStatus
How a RoleSolution was arrived at.
const RoleStatus: {
readonly Cleared: 0;
readonly BestEffort: 1;
readonly Fallback: 2;
};
Cleared: A walked candidate cleared every floor against every surface.BestEffort: Nothing cleared; the best candidate seen came back.Fallback: Nothing cleared; the stated fallback color came back.
RoleStatusValue
Type for variables that hold a RoleStatus value.
type RoleStatusValue = (typeof RoleStatus)[keyof typeof RoleStatus];
License and setup
Establish the license grant in a build script, and prepare the color utilities ahead of the first call.
ensureGrant
Obtains and verifies the license grant, once per process. Importing the build package already calls it, so a missing or refused grant fails at the import; await it by name where a script wants that step to be explicit.
In the build package only.
function ensureGrant(): Promise<void>;
initColorUtilities
Prepares the color utilities ahead of the first call. Optional: every function prepares them on first use, so call this only to move that cost earlier.
In the build package only.
function initColorUtilities(source?: string | WebAssembly.Module): Promise<void>;
source: A URL to load the compiled color utilities from, or aWebAssembly.ModulefromcompiledColorUtilities. Omit it to use the copy built into the package.
compiledColorUtilities
The compiled color utilities as a WebAssembly.Module, for passing to a worker, which then calls initColorUtilities with it and starts without compiling a second time.
In the build package only.
function compiledColorUtilities(): Promise<WebAssembly.Module>;
isInitialized
Whether the color utilities have been prepared, by initColorUtilities or by a first call.
In the build package only.
function isInitialized(): boolean;
warmColorUtilities
Runs one throwaway scheme on the worker the *Async functions use, so their first real call is faster. Optional, safe to call more than once, and safe to call anywhere, including at the top of a bundle.
In the browser package only.
function warmColorUtilities(): void;
warmColorUtilitiesAsync
Warms the worker the *Async functions use, resolving once it has answered. It never rejects. It doesn't make a synchronous call faster.
In the browser package only.
function warmColorUtilitiesAsync(): Promise<void>;