Minimal theme
Per-element overrides with theme on top of theme-minimal.
Restyle every non-data element of a plot.
Themes style everything that is not data: panel, grid, text, legend chrome, and layer defaults. Start from a preset, override individual elements with theme, and install a project-wide default with theme-set. The theming guide walks through the element model behind these examples. Every example ships its full source: click View source to copy it.
Per-element overrides with theme on top of theme-minimal.
Custom text sizes, panel fill, and grid colour passed through theme.
Per-surface horizontal alignment via element-text align: title and subtitle default left, caption right, axis titles centred.
theme-set installs a global default; explicit theme: arguments still take precedence per plot.
Side-by-side gallery of theme-bw, theme-linedraw, theme-light, theme-dark, and theme-minimal.
Pin layer-default fill, colour, and linewidth across the supporting geoms via element-geom on theme.geom.
Compose theme overrides via family-scoped theme-sub-axis, theme-sub-legend, and theme-sub-plot to keep verbose theme blocks readable.
// Same scatter rendered in three contrasting themes side-by-side.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let pts = (
(x: 1.0, y: 2.1, g: "a"),
(x: 2.0, y: 3.0, g: "a"),
(x: 3.0, y: 4.2, g: "a"),
(x: 4.0, y: 5.3, g: "a"),
(x: 5.0, y: 6.1, g: "a"),
(x: 1.5, y: 1.4, g: "b"),
(x: 2.5, y: 2.3, g: "b"),
(x: 3.5, y: 3.1, g: "b"),
(x: 4.5, y: 4.2, g: "b"),
(x: 5.5, y: 5.0, g: "b"),
)
#let panel(title, t) = plot(
data: pts,
mapping: aes(x: "x", y: "y", fill: "g"),
layers: (geom-point(size: 2.5pt),),
labels: labels(title: title, x: "X", y: "Y", fill: "Group"),
theme: t,
width: 12cm,
height: 9cm,
)
#grid(
columns: 1,
row-gutter: 0.4cm,
panel("theme-minimal", theme-minimal()),
panel("theme-classic", theme-classic()),
panel("theme-void", theme-void()),
)// theme() overrides: tune text sizes, panel background, and grid colour.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let df = range(0, 12).map(i => (x: i, y: i * i * 0.1))
#plot(
data: df,
mapping: aes(x: "x", y: "y"),
layers: (
geom-line(stroke: 1.2pt, colour: rgb("#d6604d")),
geom-point(size: 3pt, fill: rgb("#d6604d")),
),
theme: theme(
axis-title: element-text(size: 12pt),
axis-text: element-text(size: 10pt),
panel-background: element-rect(fill: rgb("#f7f0e7")),
panel-grid: element-line(colour: rgb("#d9cfbf")),
),
labels: labels(
title: "theme() Overrides",
subtitle: "Larger axis titles, a cream panel fill, and a soft grid",
x: "Step",
y: "y = 0.1 × x²",
),
width: 12cm,
height: 9cm,
)// element-text align: title/subtitle default left, caption right, axis titles
// centred; each is independent of the container and overridable per surface.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let df = range(0, 12).map(i => (x: i, y: i * i * 0.1))
#plot(
data: df,
mapping: aes(x: "x", y: "y"),
layers: (
geom-line(stroke: 1.2pt, colour: rgb("#1f77b4")),
geom-point(size: 3pt, fill: rgb("#1f77b4")),
),
theme: theme(
plot-title: element-text(weight: "bold", align: center),
axis-title-x: element-text(align: right),
),
labels: labels(
title: "Centred Title",
subtitle: "Subtitle stays left",
caption: "Caption sits right by default",
x: "Step (title pushed right)",
y: "y = 0.1 × x²",
),
width: 12cm,
height: 9cm,
)// theme-set installs a global default once; subsequent plots inherit it
// unless they pass an explicit `theme:` argument.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#theme-set(theme-minimal())
#grid(
columns: 1,
row-gutter: 0.5cm,
plot(
data: penguins,
mapping: aes(x: "flipper-len", y: "body-mass", colour: "species"),
layers: (geom-point(size: 2pt, alpha: 0.85),),
scales: scales(y: scale-continuous(labels: format-comma())),
labels: labels(
title: "Inherits the Global Theme-Minimal",
x: "Flipper Length (mm)",
y: "Body Mass (g)",
colour: "Species",
),
width: 12cm,
height: 9cm,
),
plot(
data: penguins,
mapping: aes(x: "flipper-len", y: "body-mass", colour: "species"),
layers: (geom-point(size: 2pt, alpha: 0.85),),
scales: scales(y: scale-continuous(labels: format-comma())),
labels: labels(
title: "Explicit Theme-Dark Overrides the Global",
x: "Flipper Length (mm)",
y: "Body Mass (g)",
colour: "Species",
),
theme: theme-dark(),
width: 12cm,
height: 9cm,
),
)// Gallery of theme presets, side by side, on the same data.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let pts = (
(x: 1.0, y: 2.1, g: "a"),
(x: 2.0, y: 3.0, g: "a"),
(x: 3.0, y: 4.2, g: "a"),
(x: 4.0, y: 5.3, g: "a"),
(x: 5.0, y: 6.1, g: "a"),
(x: 1.5, y: 1.4, g: "b"),
(x: 2.5, y: 2.3, g: "b"),
(x: 3.5, y: 3.1, g: "b"),
(x: 4.5, y: 4.2, g: "b"),
(x: 5.5, y: 5.0, g: "b"),
)
#let panel(title, t) = plot(
data: pts,
mapping: aes(x: "x", y: "y", fill: "g"),
layers: (geom-point(size: 2.5pt),),
labels: labels(title: title, x: "X", y: "Y", fill: "Group"),
theme: t,
width: 12cm,
height: 9cm,
)
#grid(
columns: 1,
row-gutter: 0.4cm,
panel("theme-minimal", theme-minimal()),
panel("theme-bw", theme-bw()),
panel("theme-linedraw", theme-linedraw()),
panel("theme-light", theme-light()),
panel("theme-dark", theme-dark()),
)// theme(geom: element-geom(...)) injects layer-default fill, colour, and
// linewidth into supporting geoms. Each panel below uses the same data and
// shows how a single theme override re-tints every wired geom at once.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let d = (
(q: "Q1", revenue: 10),
(q: "Q2", revenue: 18),
(q: "Q3", revenue: 25),
(q: "Q4", revenue: 22),
)
#let pts = (
(x: 1, y: 1),
(x: 2, y: 4),
(x: 3, y: 9),
(x: 4, y: 16),
)
#let make-row(title, custom-theme) = grid(
columns: 1,
row-gutter: 0.4cm,
plot(
data: d,
mapping: aes(x: "q", y: "revenue"),
layers: (geom-col(colour: black, stroke: 0.6pt),),
theme: custom-theme,
width: 12cm,
height: 9cm,
labels: labels(title: title + " (col)"),
),
plot(
data: pts,
mapping: aes(x: "x", y: "y"),
layers: (geom-line(),),
theme: custom-theme,
width: 12cm,
height: 9cm,
labels: labels(title: title + " (line)"),
),
)
#grid(
columns: 1,
row-gutter: 0.5cm,
make-row("Default theme", theme-minimal()),
make-row(
"theme(geom: element-geom(fill: red, colour: red, linewidth: 1.2pt))",
theme(
geom: element-geom(
fill: rgb("#cc3333"),
colour: rgb("#cc3333"),
linewidth: 1.2pt,
),
),
),
)// Family-scoped shortcuts compose with theme-minimal() via `+`.
#import "@preview/gribouille:0.7.0": *
#set page(width: auto, height: auto, margin: 0cm)
#let d = (
(q: "Q1", revenue: 12, segment: "Retail"),
(q: "Q2", revenue: 18, segment: "Retail"),
(q: "Q3", revenue: 25, segment: "Retail"),
(q: "Q4", revenue: 22, segment: "Retail"),
(q: "Q1", revenue: 8, segment: "Online"),
(q: "Q2", revenue: 15, segment: "Online"),
(q: "Q3", revenue: 20, segment: "Online"),
(q: "Q4", revenue: 28, segment: "Online"),
)
#plot(
data: d,
mapping: aes(x: "q", y: "revenue", fill: "segment"),
layers: (geom-col(position: "dodge"),),
labels: labels(
title: "Quarterly Revenue by Segment",
x: "Quarter",
y: "Revenue (m£)",
fill: "Segment",
),
theme: theme-minimal()
+ theme-sub-axis(
text: element-text(size: 9pt, colour: rgb("#444")),
title: element-text(size: 10pt, weight: "bold"),
)
+ theme-sub-legend(
title: element-text(weight: "bold"),
text: element-text(size: 8pt),
)
+ theme-sub-plot(title: element-text(size: 13pt, weight: "bold")),
width: 12cm,
height: 9cm,
)