Themes¶
A theme is the visual identity of a board: its typography, its canvas, its palette, and how every axis, rule, and table row is painted. You pick one with a single line at the top of the board file, and every chart on the board inherits it.
title: "Sales Dashboard" theme: neon rows: - revenue_chart
Five themes ship with dbt Charts. Everything below the theme line stays the same, so switching one is a one-word edit.
The five built-in themes¶
The same board, rendered five times. Only the theme: line differs.
Clarity's typography on a warm cream canvas, with matching gridlines and rules.
A crafted sans and a saturated palette. Loud, systematic, high contrast.
The dark theme. Near-black canvas, bright marks, inverted scaffold.
Bare chrome: plain sans, a boxed plot frame, black rules. It is the
structural root every other built-in theme extends, and it carries the same
series palette as vivid; what it strips is the scaffold, not the color.
| Theme | Voice | Canvas |
|---|---|---|
clarity |
Serif typography, gray scaffold. The default. | White |
paper |
Clarity on a warm cream background. | Cream |
vivid |
Crafted sans, loud and systematic. | White |
neon |
Crafted sans, dark. | #161616 |
stark |
Bare chrome, structural. Every other theme extends it. | White |
Those five names are the entire set. A typo, or an invented combination like
clarity-paper, is rejected at compile time rather than quietly falling back
to the default:
title: "Quarterly Report" theme: clarity-paper rows: - revenue_chart
ERR-UNKNOWN-THEME Unknown theme 'clarity-paper'. Available built-in themes:
clarity, neon, paper, stark, vivid.
Hint: Did you mean 'clarity'?
Saved inside a project, the same board reports ERR-EXTENDS-UNRESOLVED
instead: there, extends: can also name a board at the project root, so the
compiler tried that too and offers it as a fix.
What a theme owns¶
dbt Charts splits visual decisions three ways, and the split is what keeps themes swappable:
- Theme answers how the scaffold is painted: fonts, colors, surfaces, palette choices, stroke and fill, mark widths.
- Style preset answers what scaffold exists and where it sits: which side an axis is on, whether grids and domains are drawn, where the legend goes, how a title is anchored.
- Chart answers what this chart means: its query, its type, and the columns bound to each channel.
A fourth bucket is deliberately empty: some properties, compatibility surfaces
such as series, are kept out of reusable preset and theme ownership entirely.
At render time the merge order is base vega.config, then the selected theme,
so a theme wins over a project-wide Vega-Lite config default.
So a theme is the default owner of properties like style.background,
style.charts.color.gradient.palette, the visual mark.* leaves (color, fill,
stroke, opacity, stroke width), and the spark and spark_bar paint surfaces.
Concretely, changing a theme restyles:
- the board background and every card surface
- chart marks: bars, lines, areas, points
- axis grid lines, labels, and titles
- table headers, borders, and row stripes
- variable inputs: backgrounds, borders, labels
- title and body text color
A theme should not be the home for scaffolding defaults. If you find yourself
deciding where an axis sits or whether grids are shown, that belongs in a style
preset instead. For the full board-level style: inventory, see
Styling.
Custom themes¶
A custom theme is not a new file format and not a fork of a built-in. It is an
ordinary board file that extends: a theme and overrides only the keys you
name. Everything you do not name is inherited.
Extending is a deep merge¶
extends: deep-merges the boards you list, in order, with later entries
winning. Because the merge is deep, naming
style.charts.color.categorical.palette replaces exactly that leaf; the rest
of the theme's chart styling is untouched.
Here is clarity wearing one company's brand: a canvas, an accent, and a
palette. Everything else, the serif headings, the axis treatment, the type
scale, the spacing, is still clarity:
title: "Northwind Coffee, Q3"
extends: clarity
style:
background: "#fbf7f0"
accent: "#a1421f"
charts:
color:
categorical:
palette: category-6-tonal-orange
single_series_palette: category-6-tonal-orange
queries:
monthly:
columns: [month, channel, revenue]
values:
- [Jul, Wholesale, 214000]
- [Aug, Wholesale, 231500]
- [Sep, Wholesale, 248900]
- [Jul, Retail, 138400]
- [Aug, Retail, 151200]
- [Sep, Retail, 166300]
- [Jul, Subscription, 41800]
- [Aug, Subscription, 49600]
- [Sep, Subscription, 58800]
charts:
trend:
query: monthly
type: line
title: Revenue by channel
x: month
y: revenue
color: channel
style:
axis_y:
labels:
format: currency_whole
rows:
- trend
One thing worth noticing: a theme's surfaces are chosen together. Move the
canvas to a warm cream and the cool-gray table stripe inherited from clarity
will read as a cast against it, so name that too:
style: charts: table: row: stripe: color: "#f4ece0"
Lifting it into a shared file¶
Once more than one board wants that look, move the style: block into its own
board file under charts/. Prefix the filename with an underscore to mark it
as a non-listable building block: it will not appear in board listings, but
other boards can extend it.
# charts/_northwind-brand.yml extends: clarity style: background: "#fbf7f0" accent: "#a1421f" charts: color: categorical: palette: category-6-tonal-orange single_series_palette: category-6-tonal-orange rows: []
Then point boards at it. For the whole project at once, put it in
charts/meta.yml, which cascades to every board beneath it:
# charts/meta.yml extends: ./_northwind-brand.yml
Or on one board:
# charts/my-dashboard.yaml extends: ./_northwind-brand.yml
The layer already names its base theme (extends: clarity above), so a board
never lists the theme beside it. A board's own extends: merges over the
project's, key by key, which is how a dark variant becomes one line on the
boards that want it. In dbt Charts Cloud, the Design panel's Theme select and Project
Settings offer these layers by title, beside the built-ins.
Two rules that bite¶
Reach for a path ref. An entry with no / and no .yaml/.yml extension
is read as a built-in theme name first, and otherwise as a board at your
project root, not under charts/. So extends: _northwind-brand will not
find charts/_northwind-brand.yml; extends: ./_northwind-brand.yml will.
A list composes, and later entries win. extends: also takes a list, for
stacking one layer on another: a layout template under a brand layer, say.
Later entries win the style merge, and the last recognized theme name anywhere
in the chain sets the palette and paint defaults. A theme name has no place in
that list when a layer already extends it.
The Playground gallery's composition/_report-base.yml is a template any
board can extends: to inherit its theme and card spacing.
Overriding one board, or one chart¶
You do not need a custom theme to change one thing once. A board's own
style: block cascades on top of whatever theme is active, and a chart's
style: cascades on top of the board's:
theme: paper style: background: "#ffffff" charts: revenue_chart: query: monthly type: line x: month y: revenue style: color: static: "#a1421f"
Chart styles are sparse overlays: an omitted field inherits the nearest theme
or board value, and an explicit null clears a nullable one.
Where to go next¶
- Styling for the full
style:inventory, format aliases, text and border styling, and per-chart overrides. - Palettes for the shipped palette catalog with visible swatches, and how categorical, sequential, and diverging families differ.
- Tonal Foundations for the design reasoning behind the neutral scaffold colors.