Skip to content

Palettes

This page is the catalog. For how to call the resolver (palette(), color(), YAML shorthand, errors, smart defaults, scoring, anti-patterns) see Palette resolver.

Families

Every palette belongs to one of five families. The family determines how the resolver treats the palette and what kind of data it's for.

Family Purpose Used for
sequential Ordered magnitude (light → dark) Continuous numeric data without a midpoint
diverging Signed values around a midpoint Continuous numeric data with a meaningful zero or center
categorical Unordered categories Discrete enums where each value is its own identity
scaffold UI structural framing Canvas, borders, dividers, axis text; not data encoding
tone Role tokens Negative / positive / warning semantics

Scaffolds and tones use named slots (canvas, solid, text, …), not numbered stops. The other three families use ordered stops.


Sequential

Use for ordered magnitude: light at the low end, dark at the high end. All sequential palettes ship 11 stops and downsample evenly: pass steps=N to get N evenly-spaced stops from the spine.

WCAG note. Dark-end stops will fail WCAG AA (4.5:1) against #222 body text. For table cell backgrounds, use palette(name, surface="table"); see Palette resolver, which carves the spine at the contrast boundary and returns AA-safe stops. Or set an explicit text_color override on dark cells.

dbt-seq-blue: default sequential

Single-hue blue. Default for continuous numeric data without a midpoint.

step 1#e4f0fc
step 2#c1dbf5
step 3#9fc5eb
step 4#80b0de
step 5#649acf
step 6#4a85bc
step 7#3270a8
step 8#1b5b92
step 9#04477a
step 10#00345d
step 11#002341

Other sequential hues

Same 11-stop architecture, different hue anchor. Pick by editorial fit; all share the WCAG dark-end caveat above.

Palette Reads as Light end Mid (step 6) Dark end
dbt-seq-amber Honey → deep amber #f6edde #a2781a #2e1f00
dbt-seq-brown Cream → warm brown (pairs with paper theme) #f3eee6 #9b785e #341911
dbt-seq-gray Pure achromatic (magnitude without hue) #eeeeee #808080 #222222
dbt-seq-green Single-hue green #e4f3ea #44926e #002a19
dbt-seq-purple Single-hue purple #f5eaf8 #9b6ba8 #32113a
dbt-seq-rust Cream-peach → terracotta → deep rust #fdeae3 #b96648 #3e1000
dbt-seq-teal Teal / turquoise #dff3f5 #00919d #00272b

Diverging

Use for signed values around a midpoint: temperature anomalies, profit/loss, deviations from a baseline. All diverging palettes ship 11 stops with a neutral midpoint at step 6. Even-N downsamples auto-skip the midpoint (no gray middle); odd-N include it.

When to use which

Situation Palette
Default CVD-safe diverging dbt-div-blue-red
Political-sensitivity (avoid red/blue) dbt-div-orange-teal
Financial loss/gain (red negative, green positive) dbt-div-crimson-green; must pair with icon + label
Climate / atmospheric data (conventional sunset ramp) dbt-div-sunset
Scientific visualization (Moreland 2009) dbt-div-coolwarm

dbt-div-blue-red: default

Symmetric blue-to-red about a light midpoint. CVD-safe at common subset counts.

low extreme#002f55
#00508a
#2575b9
#689bcc
#a3c0dd
midpoint#e4e4e4
#dcb1a9
#c88074
#b04f41
#852c21
high extreme#590b05

Other diverging palettes

Palette Low extreme Mid High extreme Notes
dbt-div-orange-teal #4c1f00 #e4e4e4 #003634 Symmetric, fully CVD-safe; non-politically-loaded
dbt-div-crimson-green #540a31 #e4e4e4 #00481d Engineered red↔green CVD pass via lightness asymmetry; icon + label required
dbt-div-sunset #125a56 #eceada #a01813 Paul Tol multi-hue ramp; CVD-safe; conventional in climate work
dbt-div-coolwarm #3b4cc0 #dddddd #8b0000 Moreland 2009; canonical in ParaView/VTK and scientific viz

Categorical

Use for unordered categories: series in a multi-line chart, slices in a stacked bar, items in a legend. Categorical palettes ship a fixed number of slots; steps=N returns the first N. There is no interpolation: stops are what you get.

One value, one color, across the whole board

A palette says which colors are available. It does not, on its own, say which category gets which one, and if each chart decides that for itself, the same category drifts between charts. Electronics ends up blue on the bar chart and green on the line chart, and the reader has to re-read every legend instead of trusting the color.

dbt Charts settles the question once per board. Before any chart renders, the engine reads the executed rows, collects the distinct values of every categorical field, and assigns each value a palette slot. Every chart that draws that field then paints it from that slot, so a value keeps its color everywhere, including on charts whose data is missing some of the values.

What is shared is the slot, not the hex. Everything a chart derives from its palette is indexed the same way (the fill, the darker ink its direct labels use, the swatch in an attached table), so those stay in step with each other automatically.

Three consequences worth knowing:

  • One chart's data never disturbs another's colors. A chart filtered down to two of five categories draws those two in the colors they have everywhere else, rather than sliding them up into the first two slots.
  • A nested board keeps its own theme. If part of a board sets a different theme, a shared value takes the same slot in each theme's palette rather than being repainted in the outer theme's colors. The two halves agree on which position a value owns; each still looks like the theme it asked for. A chart whose own palette is too short to reach the slot a value owns is the one exception: it declines the binding and keeps the coloring it would have had, rather than wrapping around and giving two categories one color.
  • It only ever re-colors a color: channel a chart already has. A chart that identifies a category some other way (a single-metric bar with x: category and no color:) keeps the ink it has today (see single_series_palette). Giving it a color channel it never asked for would override authored static colors, so the binding leaves it alone.

The threshold

A field is bound when two or more charts on the board use it as their color: channel. A field only one chart colors by has no cross-chart consistency to preserve, so it keeps the chart-local coloring it would have had. This keeps small boards from acquiring bindings they gain nothing from.

A chart with authored layers: is the exception: it spends its color channel on telling the layers apart, so it neither binds nor counts toward the two. Putting layer names and category names in one legend would be the wrong picture.

Pinning a category

To fix a specific value to a specific color, author it under style.charts.category_colors, keyed by data field. The keys under values: are the literal data values the field takes in your query results: write them exactly as the query returns them, not a display label.

style:
  charts:
    category_colors:
      category:
        values:
          Electronics: category[1]
          Accessories: category[2]
          Tools: dbt-grays.muted

Values accept a palette token (resolved against the board's theme, so a theme swap re-skins the board) or a literal hex. Anything else is rejected at compile time rather than silently ignored.

A few rules follow from the block being intent rather than a cache:

  • Authoring a field binds it, even on a single chart: the threshold only gates automatic binding.
  • Authored entries take the first slots. Values found in the data but absent from the block are assigned after them, in first-seen order.
  • A pinned color is never reused for a discovered value, so no two categories of a bound field share a swatch. Pinning two drawn values to the same color is an error rather than a silent collision.
  • A pin naming a palette color pins the slot, not the ink. category[2] and the hex that category[2] resolves to mean the same thing: this value owns slot 2. A color the palette does not contain is taken literally.
  • A pin naming a value the render does not draw is skipped, and warns. It claims no swatch, so nothing else shifts. dbt Charts cannot tell a misspelled pin from a category a variable filter excluded today, so it declines to blank the board over the difference and raises WARN-CATEGORY-COLOR-PIN-UNSEEN naming the value instead.

Pinning is also what makes an assignment survive new data. Slots are handed out in the order values are first seen, so a category that shows up for the first time partway through a chart's rows takes the slot at that position and pushes the values after it down one. Within a single render everything stays consistent either way; pinning is what holds a color still from one week to the next.

If a field has more distinct values than the palette has swatches, what happens depends on who asked for the binding:

  • You authored the field. dbt Charts raises. You named it, so quietly not honoring the pins would be the wrong kind of silence. Widen the palette, pin fewer values, or reduce the distinct count in the query.
  • The engine bound it on its own. It declines instead: the field keeps the per-chart coloring it had before, which is also the only place two categories can still land on the same swatch, because each chart cycles the palette independently. Binding is something the engine volunteers; it would rather hand the board back unchanged than seat two categories on one color.

Either way the swatch count is the ceiling: nothing cycles inside a bound field.

editorial-10: default for clarity themes

Quieter ten-color set designed for cycling: the engine fills slots in order without a human picker. It is the categorical family for the shipped clarity and paper themes.

blue#40639c
sky#779bc9
green#608470
purple#775770
gold#d49656
rust#ae6349
teal#a0b6b7
brown#ad9c7f
gray#7a8895
graphite#5c6668

The order is blue, sky, green, purple, gold, rust, teal, brown, gray, graphite. Every companion uses the same positions. Public roles provide stable semantic access, including orange for rust, sage for teal, and charcoal for graphite. cyan and moss are also accepted as names for the sky and sage slots. Prefer roles such as category.green when hue meaning matters; numeric slots preserve position, not semantic intent, across palette retunes.

Reach for editorial-10 (over vivid-10) when:

  • The dashboard is meant to read as editorial: calmer, lower-chroma, fewer competing hues.
  • Charts should accompany a dark single-series mark without competing with it.
  • Cross-chart palette consistency matters and you'd rather not hand-pin per chart.

Stay on vivid-10 when:

  • A chart needs up to ten categories and editorial restraint isn't a goal. vivid-10 keeps all ten hues apart for typical vision. For color-vision-deficient readers, the first five series clear the Leonardo gate (ΔE 11) and all ten keep a floor of ΔE 9; past five series, add direct labels or group the tail.
  • The product surface is brand-default rather than editorial.

Variants: dark, light, pale, deep

Categorical only. Every categorical palette -- shipped or inline (palette: ["#4e79a7", "#f28e2b", ...]) -- derives four literal tiers live, computed from its own stops (variant(), no companion file to author or keep in sync). Address one with a trailing token segment, on any addressing form:

Form Example
Role + alias category.blue.dark
Role + slot category[2].light
Palette name + slot vivid-10.3.pale
Whole list category.deep, vivid-10.pale

The four words are literal, not a contrast direction: dark is never lighter than its base and light never darker, on every theme. Which variant a board or theme reaches for on a dark canvas -- so a mark stays legible, or a mark stays deliberately recessive -- is an authoring decision; the tier itself does not flip.

A continuous (sequential/diverging) palette's own -dark fork (dbt-seq-blue-dark, ...) is a different, older naming convention: a real, separately-shipped palette name, not this grammar's .dark segment. A continuous palette's own stops already encode a monotonic lightness order, so the variant grammar does not apply to it -- writing dbt-seq-blue.dark raises rather than silently resolving.

Tier Reads as Typical use
dark A notch darker than the base, chroma held Direct-label ink, contrast on light canvases
light Moderately lighter, chroma reduced A recessive twin that still carries its hue
pale Very light, low chroma, one uniform band -- every stop converges to the same target lightness, regardless of the base Dim-the-chorus / de-emphasis highlighting
deep Very dark, chroma held, converges toward a dark pole (not a fixed band -- a darker base still ends up darker than a lighter one) Single-series ink

Direct-label ink (endpoint labels, stacked-bar segment names, pie slice labels, support-table header swatches) is not sourced from the dark variant -- the engine derives it live from each mark color against the chart's own canvas (label_ink()), which is what keeps labels legible on dark themes like neon with no theme-level rebind.

A few editorial-10 slots, tiers computed rather than hand-tuned:

Slot editorial-10 .dark .light .pale .deep
1 #40639c blue #284980 #7a90b4 #c5d2e7 #0e2e63
5 #d49656 gold #a16723 #ddb793 #e2cdb9 #543000
10 #5c6668 graphite #434d4f #8b9192 #ced2d2 #2a3335

single_series_palette: ink for single-series charts

palette (categorical, above) is the color source for charts with a color: channel; dbt Charts indexes into it once per distinct series value. single_series_palette is the separate ink source for charts that have no color: channel at all: a plain single-metric bar, line, or area. There's no series to pick a slot by value, so dbt Charts assigns each single-series chart a slot by its position in the board: the compiler walks the board tree in reading order and hands out single_series_palette[0], single_series_palette[1], and so on, wrapping back to the start once the list is exhausted. Two single-series bar charts on the same board get two different inks; the same chart re-rendered always gets the same ink, because the slot comes from tree position, not from data.

Set it at the theme or board level under style.charts, as a list of color tokens or a palette name (expanded to that palette's stops at validation time). Theme-relative role tokens resolve against the active theme, so category.sky.deep selects editorial sky ink under Clarity and vivid sky ink under Vivid.

style:
  charts:
    single_series_palette: editorial-10.deep
    # or a role-token list:
    # single_series_palette: [category.sky.deep, category.sage.deep, category.green.deep]

A chart-local style.color.static or style.color.categorical override always wins over the rhythm slot; use it when one specific chart needs to break from the cycle (for example, to match an established brand color for that metric).

The rhythm slot is unaffected by the board-wide category binding: that binding only re-colors a color: channel a chart already has, and a single-series chart has none. See One value, one color, across the whole board.

vivid-10: general-purpose ten-color

Blue-led and green-third: the five most chromatic hues run first, then purple, moss, brown, and the two neutrals. Purple and moss are tuned against color-vision deficiency rather than for chroma, which is why they follow the lead five. Brown and charcoal sit at slots 8 and 10 because they are the only two stops that fall under 3:1 against a near-black canvas, so a chart needs eight series before it draws one. The default categorical palette for the vivid and neon themes; editorial-10 (above) is the categorical family for clarity and paper.

blue#0073c2
cyan#00c8ee
green#00ad75
gold#e1a500
orange#da5a23
purple#a86c9d
moss#6a8f47
brown#7a5531
gray#949daa
charcoal#505b6b

Every slot has all four variants: vivid-10.1.dark, vivid-10.1.light, vivid-10.1.pale, vivid-10.1.deep. Direct-label ink is not sourced from the dark variant -- the engine derives it live from each mark color against the chart's own canvas.

hero-6: single dominant series

A six-color hero-blue-versus-neutrals palette for charts where one series should dominate. Slot 1 is the only saturated color; slots 2–6 are gray/brown neutrals that step into warmth.

hero blue#2768a3
light gray#acb7c2
gray#929aa6
brown#a68d72
beige#b8a593
sand#c7a987

Contract. The hero blue is both the most saturated and the darkest color in the palette; that's the perceptual rule that makes slot 1 win. Don't reorder; the support neutrals are designed to lie behind it.

Tonal monochrome: category-6-tonal-*

Single-hue categorical palettes for editorial / brand-restrained dashboards. Five hues ship: blue, green, purple, orange, brown, each anchored near a vivid-10 hue so they coordinate with the default categorical system -- except purple, which keeps its own hue, about 14° from vivid-10's purple.

Palette Anchor color Reads as
category-6-tonal-blue #0375c4 All-blue
category-6-tonal-green #00875a All-green
category-6-tonal-purple #9650a8 All-purple
category-6-tonal-orange #b74c1f All-orange
category-6-tonal-brown #9a642b All-brown (paper-theme companion)

Contract: 4 strict-safe + 2 extended. Every tonal palette ships 6 stops with a layered contract:

Slots Role CVD safety
0–3 (core) strict-safe Color alone sufficient; pairwise CVD ΔE ≥ 11
4–5 (extended) form-redundant only Below the strict gate. Require dash, marker, or pattern redundancy when used in a chart

The 6-stop cap is structural: at fixed hue, English's color vocabulary saturates around 4–5 stops ("two blues that are 'just blue'"), and CVD simulation collapses adjacent stops. If a chart needs 7+ categories, use vivid-10 instead.

When to use. Editorial / branded dashboards where chromatic restraint matters more than category count. Small-multiples and KPI tiles where the color budget is tight. Charts with direct labels or stable category order ; the legend reads via labels, not by glance. Avoid for many-slice pies, dense scatters, or stacked bars with more than four segments.

Brown ↔ cream bridge. category-6-tonal-brown's extended slots (4–5) are deliberately hue-shifted to overlap the dbt-creams scaffold region. On the paper theme, slot 5 visually merges with the surrounding chrome; that's the design intent. Use slot 5 only with form redundancy on paper backgrounds.

Compatibility palettes

Palette Use
tableau Tableau-style 10-color set. Retained for users who want the familiar Tableau hues instead of vivid-10.

Scaffold

Scaffolds are UI structural framing: canvas, borders, dividers, axis labels, table stripes, secondary text. Not data encoding. Address them through named aliases (chrome.canvas, chrome.ink, etc.) rather than by integer slot.

dbt-grays: neutral chrome

Twelve steps from near-white to near-black. The darkest step, void, is the near-black canvas for dark themes; clarity, vivid, and other light themes use the lighter steps for chrome.

canvas#FAFAFA
surface#F7F8FA
grid#EDEFF2
border#DFE1E5
subtitle#8A8C8F
muted#626366
heading#313233

dbt-creams: warm chrome

Same twelve-step shape as dbt-grays, in warm cream tones. Default chrome for the paper theme.

canvas#FAF7F0
surface#F5F2EB
grid#E9E1D2
border#DDD3C0
subtitle#918878
muted#6F685D
heading#403B37

Tone

Tone palettes encode role semantics: info, negative, positive, warning. Six named slots per palette: bg, subtle, border, solid, solid-hover, text. Address them through color(), not palette().

Contract. All tone slots are designed so canonical pairs (text-on-bg, solid-on-white) pass WCAG AA. Tone color alone is insufficient communication; always pair with icon + label per the standard accessibility discipline.

info: blue / information / neutral status

Informational notes, neutral status messages, non-error callouts.

Alias Hex Role
bg #e7f4ff Very light blue background
subtle #d6ecff Secondary fill or hover-on-bg
border #6bb9f8 Mid-tone divider / outline
solid #008cdd Canonical "information" fill
solid-hover #0070b3 Darker variant for button hover state
text #003659 AA-contrast text color on .bg and on white

negative: red / stop / bad

Critical errors, destructive actions, losses. Western convention: red = stop/bad.

Alias Hex Role
bg #ffedec Very light peach tint for surface backgrounds
subtle #ffdbd8 Hover-on-bg or secondary fill
border #e79491 Mid-tone divider / outline
solid #94001e Canonical "this is negative" fill
solid-hover #6e0014 Darker variant for button hover state
text #4b000a AA-contrast text color on .bg and on white

positive: green / go / good

Successful actions, gains, completions. Western convention: green = go/good.

Alias Hex Role
bg #e3f8e9 Very light green background
subtle #cff2da Secondary fill / hover-on-bg
border #82cb9b Mid-tone divider / outline
solid #00884d Canonical "all good" fill
solid-hover #006d3c Darker variant for button hover state
text #003e20 AA-contrast text color on .bg and on white

warning: amber / caution

Pay attention; something needs input, nothing's broken. Western convention: amber = caution.

Alias Hex Role
bg #fff3e5 Very light cream-amber background
subtle #ffe7ca Secondary fill / hover-on-bg
border #f0b871 Mid-tone divider / outline
solid #e69812 Canonical "pay attention" fill
solid-hover #c68100 Darker variant for button hover state
text #643f00 AA-contrast text color on .bg and on white

Further reading

  • Palette resolver: how to call palette() / color() from Python or YAML, plus error semantics and the anti-pattern list.
  • Tonal foundations: the neutral-frame thinking that pairs with the chrome scaffolds.