Palettes & Gradients β
Tools for building multi-color palettes and exporting them:
| Tool | Purpose |
|---|---|
generate_palette | Color-theory harmonies from one base color |
generate_palette_with_locks | Fill a palette around colors you want to keep |
generate_gradient | Interpolate between 2β32 stops in 7 color spaces |
export_palette | Write colors as CSS, SCSS, Tailwind, W3C tokens, JSON or a DESIGN.md block |
For design-system scales and semantic colors, see Visual Cohesion. For Material 3 schemes, see Material Design. All tools accept any supported color syntax (see Color input).
generate_palette β
Generates a harmony (analogous, triadic, etc.) from a base color. It works in perceptual HCT space by default, avoids "bile zone" dark yellow-greens, and by default describes each swatch so an agent can tell what it produced.
Parameters β
| Parameter | Type | Required | Default | Limits | Description |
|---|---|---|---|---|---|
allowDisliked | boolean | no | false | β | Allow dark yellow-green "bile zone" colors |
baseColor | string | yes | β | β | Base color for palette generation |
count | number | no | 5 | 3β12 | Number of colors to generate |
includeDetails | boolean | no | true | β | Include perceptual name, temperature, tone and best text ink per swatch |
space | string | no | "hct" | enum: hct | oklch | ryb | hsl | Color wheel to rotate on |
type | string | no | "analogous" | enum: monochromatic | analogous | complementary | split-complementary | triadic | tetradic | square | compound | shades-and-tints | Type of color harmony |
Harmony types β
type | Hue offsets from the base (in order) |
|---|---|
analogous | 0, β30, +30, β60, +60 |
complementary | 0, 180 |
split-complementary | 0, 150, 210 |
triadic | 0, 120, 240 |
tetradic | 0, 60, 180, 240 |
square | 0, 90, 180, 270 |
compound | 0, 30, 180, 210, β30 |
monochromatic | base hue only; tone ramp |
shades-and-tints | base hue Β±7Β°; tone ramp |
How the swatches are built:
- Hue-rotation types. Swatch 1 is the base color, unchanged. The other swatches take the offsets in order and alternate tone by +8 / β8 so neighbours separate. When
countis larger than the offset list, the list repeats with tones Β±22 and 78 % of the chroma. Inhctspace tones are kept within 16β90 and chroma stays at least 12. monochromatic/shades-and-tints. The base color itself is not included. Swatches run evenly from tone 92 (light) to tone 14 (dark), with chroma tapered toward both ends.shades-and-tintsalso shifts the hue by up to Β±7Β° across the ramp for warmer highlights and cooler shadows.- Spaces.
hct(default) andoklchrotate perceptual hue.hslrotates the HSL wheel.rybrotates the painter's red-yellow-blue wheel (red β green, blue β orange, yellow β violet). Formonochromaticandshades-and-tints,rybgives the same colors ashct. - Disliked colors. Unless
allowDislikedistrue, a generated swatch that lands in the dark yellow-green "bile zone" is lightened to tone 70. In the hue-rotation types the base (swatch 1) is kept as-is even when it is disliked:#6b6a1ewithanalogousstill starts with#6b6a1e.
Examples β
Default (analogous, HCT, with details):
{ "baseColor": "#6750a4" }Generated analogous palette (HCT):
1. #6750a4 β medium dark violet (cool, tone 40, best text: #ffffff)
2. #4d70be β blue (cool, tone 48, best text: #ffffff)
3. #713277 β dark magenta (neutral, tone 32, best text: #ffffff)
4. #007aab β sky-blue (cool, tone 48, best text: #ffffff)
5. #802e4f β dark red (warm, tone 32, best text: #ffffff)
Notes:
- Balanced perceptual lightness and chroma in HCT space so harmonies maintain even visual weight.Monochromatic ramp (the base #6750a4 itself is not in the list):
{ "baseColor": "#6750a4", "type": "monochromatic", "count": 5 }Generated monochromatic palette (HCT):
1. #eee4ff β very light muted plum (neutral, tone 92, best text: #0f0f17)
2. #bba7f0 β medium light violet (cool, tone 73, best text: #0f0f17)
3. #8871c5 β violet (cool, tone 53, best text: #ffffff)
4. #564389 β dark violet (cool, tone 34, best text: #ffffff)
5. #29194f β very dark soft violet (cool, tone 14, best text: #ffffff)Painter's wheel complement, 4 colors (the second cycle is lighter and softer):
{
"baseColor": "#e63946",
"type": "complementary",
"space": "ryb",
"count": 4,
"includeDetails": false
}Generated complementary palette (RYB):
1. #e63946
2. #66eb5c
3. #e9a5aa
4. #aae9a5
Notes:
- Computed on the traditional artistic RYB color wheel (redβgreen, blueβorange, yellowβviolet).Notes β
- The bile-zone fix lifts dark swatches to tone 70, which can flatten a ramp. With base
#6b6a1e(a disliked dark olive),monochromaticreturns#eeebad, #b7b56f, #b0af5e, #b0af66, #b0ae78: the three darkest swatches become near-identical. WithallowDisliked: trueyou get the full rampβ¦ #838236, #525112, #262500. countoutside 3β12 fails schema validation (count: Too small: expected number to be >=3). An invalid base color givesInvalid color format: blah.
generate_palette_with_locks β
Builds a palette of totalColors that contains every color in lockedColors and fills the remaining slots. The search is deterministic, so the same input always gives the same palette.
Parameters β
| Parameter | Type | Required | Default | Limits | Description |
|---|---|---|---|---|---|
colorSpace | string | no | "oklch" | enum: hsl | lab | hct | oklch | Color space for generation/interpolation |
lockedColors | array | yes | β | 1β19 items, strings | Colors that must be included |
mode | string | no | "harmony" | enum: harmony | contrast | gradient | harmony, contrast, or gradient (see below) |
totalColors | number | yes | β | 2β20 | Total number of colors in the final palette |
lockedColors must contain fewer colors than totalColors, so at least one color is generated.
Modes β
mode | How new colors are chosen | Uses colorSpace | Output order |
|---|---|---|---|
harmony | Candidates sit at harmonic hue offsets from the first locked color (Β±30, Β±60, 90, 120, 150, 180, 210, 240, 270) at five lightness levels. Each slot takes the candidate with the largest minimum ΞE2000 to every color chosen so far. | oklch, hct, hsl; lab behaves like oklch | locked colors first, then generated |
contrast | Same max-min ΞE2000 pick from a fixed OKLCH grid (hue every 15Β°, L 0.38/0.56/0.74/0.88, chroma 0.10/0.17). | ignored | locked colors first, then generated |
gradient | Interpolates between consecutive locked colors. Needs β₯ 2 locked colors. Leftover slots go to the earliest segments. | oklch, hct, lab (CIELAB), hsl | gradient order: lock 1, in-betweens, lock 2, β¦ |
Because harmony picks the most distinct candidate for each slot, its results are varied rather than subtle. For close variations of one color, use generate_tonal_scale or generate_palette with type: "monochromatic". Generated colors avoid the disliked bile zone in harmony (oklch/hct/lab) and contrast mode.
Examples β
Harmony around one locked color:
{ "lockedColors": ["#6750a4"], "totalColors": 5 }Generated palette with 1 locked colors:
1. #6750a4 (locked)
2. #a3ac41
3. #282b00
4. #ea807b
5. #39b0e8
Mode: harmony
Color space: oklch
Total colors: 5Gradient through three locked colors (2 + 2 in-betweens):
{
"lockedColors": ["#1e3a8a", "#fbbf24", "#be123c"],
"totalColors": 7,
"mode": "gradient"
}Generated palette with 3 locked colors:
1. #1e3a8a (locked)
2. #95499a
3. #ea6e68
4. #fbbf24 (locked)
5. #f08b00
6. #df5218
7. #be123c (locked)
Mode: gradient
Color space: oklch
Total colors: 7Notes β
- Locked colors are echoed exactly as you wrote them, not normalized:
["red", "navy"]produces1. red (locked)β¦4. navy (locked). - Errors (
isError: true):Error: Number of locked colors (2) must be less than total colors (2)Gradient mode requires at least 2 locked colorsInvalid color format: nopetotalColorsabove 20 or more than 19 locked colors fail schema validation.
generate_gradient β
Interpolates a gradient through 2β32 color stops and returns it as a numbered list, a hex list, or a CSS gradient.
Parameters β
| Parameter | Type | Required | Default | Limits | Description |
|---|---|---|---|---|---|
colors | array | yes | β | 2β32 items, strings | Color stops, in order |
easing | string | no | "linear" | enum: linear | ease-in | ease-out | ease-in-out | Easing function for the transition |
format | string | no | "array" | enum: hex | css-linear | css-radial | array | Output format |
interpolation | string | no | "lab" | enum: rgb | hsl | lab | lch | hct | oklab | oklch | Color space for interpolation |
steps | number | yes | β | 3β100 | Total number of output colors, including every input color |
How steps works β
stepsis the total length of the output, and every input color appears in it as a stop.stepsmust be β₯ the number of colors. With exactly that many, the output is just your stops. A lower value is an error:steps (3) must be at least the number of input colors (4).- The
steps β 1intervals are split across the segments, and any remainder goes to the earliest segments. For example, 3 stops withsteps: 6puts 3 intervals in the first segment and 2 in the second.
Interpolation spaces β
All hue-based spaces take the shorter way around the hue circle. In oklch, lch and hct, a grey endpoint borrows the other endpoint's hue so the gradient doesn't swing through an unrelated hue.
Blue β yellow, steps: 5, format: "hex":
interpolation | Output |
|---|---|
rgb | #0000ff, #4040bf, #808080, #bfbf40, #ffff00 |
hsl | #0000ff, #00bfff, #00ff80, #40ff00, #ffff00 |
lab (default) | #0000ff, #9850d5, #ca8aaa, #eac476, #ffff00 |
lch | #0000ff, #ff00ab, #ff0050, #ffa100, #ffff00 |
hct | #0000ff, #007bae, #00afaf, #00e779, #ffff00 |
oklab | #0000ff, #1d77e7, #6cabc7, #b5d797, #ffff00 |
oklch | #0000ff, #0087ac, #00baae, #19f268, #ffff00 |
rgb passes through grey. lab and oklab cut straight through the color solid. lch, hct, oklch and hsl travel around the hue circle, and which way they go depends on each space's hue angles.
Easing β
Easing remaps the position t (0β1) within each segment: ease-in = tΒ², ease-out = t(2 β t), ease-in-out = 2tΒ² for t < 0.5, otherwise β1 + (4 β 2t)t. Black β white, steps: 5, lab:
easing | Output |
|---|---|
linear | #000000, #3b3b3b, #777777, #b9b9b9, #ffffff |
ease-in | #000000, #141414, #3b3b3b, #878787, #ffffff |
ease-out | #000000, #676767, #b9b9b9, #ededed, #ffffff |
ease-in-out | #000000, #212121, #777777, #dbdbdb, #ffffff |
Because easing restarts at every stop, black β white β black with ease-in and steps: 9 gives #000000, #141414, #3b3b3b, #878787, #ffffff, #ededed, #b9b9b9, #676767, #000000.
Output formats β
array (the default) is a numbered text report, not JSON:
{ "colors": ["#0000ff", "#ffff00"], "steps": 5 }Generated gradient with 5 steps:
1. #0000ff
2. #9850d5
3. #ca8aaa
4. #eac476
5. #ffff00
Interpolation: lab
Easing: linear
Input colors: #0000ff β #ffff00css-linear (always 90deg) and css-radial (always circle) place the output colors at evenly spaced percentages:
{
"colors": ["#6750a4", "#ffffff", "#006a6a"],
"steps": 7,
"interpolation": "oklch",
"format": "css-linear"
}linear-gradient(90deg, #6750a4 0.0%, #9689c4 16.7%, #c9c3e2 33.3%, #ffffff 50.0%, #b2cccb 66.7%, #679a99 83.3%, #006a6a 100.0%){
"colors": ["#ff6b6b", "#4ecdc4"],
"steps": 3,
"format": "css-radial",
"interpolation": "oklab"
}radial-gradient(circle, #ff6b6b 0.0%, #c2a599 50.0%, #4ecdc4 100.0%)hex is a comma-separated list, as in the tables above.
Uneven segments in CSS
Percentages are spaced evenly across all output colors, so a middle stop only lands at 50 % when both segments have the same number of intervals. With 3 stops and steps: 6, #ffffff sits at 60 %: linear-gradient(90deg, #6750a4 0.0%, #9b87c2 20.0%, #cdc2e1 40.0%, #ffffff 60.0%, #8db2b2 80.0%, #006a6a 100.0%).
Errors β
- Fewer than 2 colors,
stepsbelow 3 or above 100, or an unknown enum fail schema validation (Invalid arguments for tool 'generate_gradient': β¦). stepsbelow the number of colors:steps (3) must be at least the number of input colors (4).- An unparsable stop:
Invalid color format: β¦.
export_palette β
Writes a list of colors in a format you can paste into code or config. Every color is normalized to lowercase #rrggbb.
Parameters β
| Parameter | Type | Required | Default | Limits | Description |
|---|---|---|---|---|---|
colors | array | yes | β | 1β256 items, strings | Palette colors |
format | string | yes | β | enum: css | scss | tailwind | tokens | json | design-md | Output format |
names | array | no | β | β€ 256 items, strings; length must equal colors | Name for each color. Omitted: 50/100β¦900 scale (see below) |
prefix | string | no | β | β | Prefix for variable/token names (e.g. brand) |
Naming rules β
- Slugification.
namesandprefixare turned into token-safe slugs: diacritics are stripped, text is lowercased, every run of characters outsidea-z0-9becomes-, and leading and trailing dashes are trimmed."Secondary Color"βsecondary-color,"TΓ©rtiary!"βtertiary,"Brand"βbrand. - Default names follow the color's position:
50, 100, 200, β¦ 900for the first 10 colors, then1100, 1200, β¦(950and1000are never used). They're assigned by position, not by lightness. A name that is missing, empty, or slugifies to nothing ("","!!!") gets its position's default. - Prefix. In
css,scss,jsonanddesign-mdthe prefix is prepended as<prefix>-<name>. Intailwindandtokensit becomes the group key instead; without a prefix the key ispalette(Tailwind) orcolor(tokens). A prefix that slugifies to nothing is ignored. - Duplicates are an error. If two colors end up with the same name after slugification, the call fails with
Duplicate token names after normalization: "primary" (colors 1 and 2). Give each color a distinct name.Clashes with a default name count too:names: ["100", "", "x"]fails because color 2 defaults to100.
Formats β
Real output for:
{
"colors": ["#6750a4", "#625b71", "#7d5260"],
"names": ["Primary", "Secondary Color", "TΓ©rtiary!"],
"prefix": "Brand",
"format": "css"
}css:
:root {
--brand-primary: #6750a4;
--brand-secondary-color: #625b71;
--brand-tertiary: #7d5260;
}scss:
$brand-primary: #6750a4;
$brand-secondary-color: #625b71;
$brand-tertiary: #7d5260;tailwind (snippet for theme.extend.colors, printed exactly like this):
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
"brand": {
"primary": "#6750a4",
"secondary-color": "#625b71",
"tertiary": "#7d5260",
},
},
},
},
};tokens (W3C Design Tokens draft: $type / $value):
{
"brand": {
"primary": {
"$type": "color",
"$value": "#6750a4"
},
"secondary-color": {
"$type": "color",
"$value": "#625b71"
},
"tertiary": {
"$type": "color",
"$value": "#7d5260"
}
}
}json (hex, rgb and integer hsl per color):
{
"brand-primary": {
"hex": "#6750a4",
"hsl": "hsl(256, 34%, 48%)",
"rgb": "rgb(103, 80, 164)"
},
β¦
}design-md (a colors: block for the YAML front matter of a DESIGN.md file; values are quoted so # isn't read as a YAML comment):
colors:
brand-primary: "#6750a4"
brand-secondary-color: "#625b71"
brand-tertiary: "#7d5260"With default names and no prefix, keys that start with a digit are quoted too:
{ "colors": ["#eee4ff", "#bba7f0", "#8871c5"], "format": "design-md" }colors:
"50": "#eee4ff"
"100": "#bba7f0"
"200": "#8871c5"TIP
Without prefix, default names are bare numbers (--50, $50, "50"). Pass a prefix (e.g. "gray" β $gray-50, $gray-100, β¦) to get namespaced variable names.
Errors β
| Input | Message (isError: true) |
|---|---|
names length β colors length | Error: names array length (1) must match colors length (2). |
| two names with the same slug | Duplicate token names after normalization: "primary" (colors 1 and 2). β¦ |
| unparsable color | Invalid color: nope |
unknown format | Invalid arguments for tool 'export_palette': format: Invalid option: β¦ |
Related β
- Visual Cohesion: tonal scales, state colors, semantic palettes, cohesion scoring
- Accessibility & CVD: check a palette for color-blind collisions
- Material Design: Material 3 tonal palettes and themes
- DESIGN.md: audit the exported
colors:block - Color Spaces, HCT