library(ggplot2)
p <- ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length")
# the house default: airy, modern, light gridlines
p + theme_minimal()
Swap themes, kill the grey background, and set panel colours — live in your browser
Learn to restyle a ggplot2 plot in R — switch to a built-in theme (theme_minimal, theme_bw, theme_classic), remove the default grey background, change the panel and plot background colour and grid lines, scale all text with base_size, set a theme for the whole session, and build your own. Edit and run the code live, no install needed.
June 20, 2026
July 7, 2026
theme_minimal(), theme_bw(), theme_classic(), theme_void(), and more.theme_gray(); switch to theme_bw()/theme_classic() (or blank the panel) for a white background.theme(...) and the four helpers: element_rect() (backgrounds), element_line() (grids/ticks), element_text() (labels), element_blank() (remove).base_size, e.g. theme_minimal(base_size = 16).theme_set(), or build your own by extending a base theme with %+replace%.A ggplot2 theme is the styling layer of your plot: it sets the background colour, the grid lines, the fonts, the axis ticks, and the legend look — everything that isn’t the data itself. The most common search that lands here is “how do I get rid of the grey background?”, and the answer is almost always a theme. In R, ggplot2 ships a gallery of ready-made themes you apply in a single layer, plus a theme() function to override any individual element. This lesson walks through both; the figures are rendered right here, and you can re-run or edit any of them live.
We use the built-in ToothGrowth dataset: the length of odontoblast cells (len) in guinea pigs given vitamin C at three doses (dose: 0.5, 1, or 2 mg/day) via two delivery methods (supp: orange juice OJ or ascorbic acid VC). A grouped box plot gives us plenty of chrome — panel, grid, legend, text — to restyle.
The fastest way to change a plot’s look is to add a complete theme as a layer. ggplot2 ships several: theme_gray() (the default), theme_bw(), theme_linedraw(), theme_light(), theme_minimal(), theme_classic(), theme_void(), and theme_dark(). Here is the same plot under four of them so you can compare at a glance.

Swap theme_minimal() for theme_bw() to get a crisp white panel with a thin border, or theme_classic() for a stripped-back look with only the two axis lines:

For richer, hand-tuned third-party themes (Tufte, Economist, WSJ, Stata), the ggthemes package adds a theme_*() family on the same pattern. We keep teaching code on the built-in set — theme_minimal() is the Datanovia house default — but the mechanics below apply to any theme.
By far the most common request: the default theme_gray() panel is grey, and you want it white. You have three clean options. The simplest is to switch to a theme that is already white — theme_bw() or theme_classic(). The most surgical is to keep your current theme and blank just the panel background with element_blank().

element_blank() is the universal “remove this element” tool — it works on any theme component, not just backgrounds.
The panel is the rectangle directly behind your data. Set its colour with panel.background = element_rect(fill = ..., colour = ...), and style the grid lines with panel.grid.major / panel.grid.minor = element_line(...). element_rect() controls rectangular fills and borders; element_line() controls lines.
library(ggplot2)
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme(
panel.background = element_rect(fill = "#eaf2fb", colour = "grey70"),
panel.grid.major = element_line(colour = "white", linewidth = 0.6),
panel.grid.minor = element_line(colour = "white", linewidth = 0.3)
)
The plot background is the whole image, including the margins around the panel — distinct from the panel itself. Set it with plot.background = element_rect(fill = ...). Colour the two separately to see the difference clearly.
To strip a plot down, blank the grid lines and panel border with element_blank(), then add back the two axis lines with axis.line = element_line(...) — this is essentially how theme_classic() is built.
library(ggplot2)
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme_minimal() +
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
axis.line = element_line(colour = "black")
)
Every override inside theme() is built from one of four helper functions — learn these and you can style anything:
element_rect() — rectangles: backgrounds, borders, legend keys (fill, colour, linewidth).element_line() — lines: grid lines, axis lines, ticks (colour, linewidth, linetype).element_text() — text: titles, axis labels, legend text (colour, size, face, angle).element_blank() — removes the element entirely.Here all four appear in one theme() call:
library(ggplot2)
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme(
panel.background = element_rect(fill = "#f7f9fc", colour = "grey80"), # rect
panel.grid.major = element_line(colour = "#cdd9ea"), # line
axis.title = element_text(face = "bold", colour = "#1f3a5f"), # text
panel.grid.minor = element_blank() # blank
)
Every built-in theme takes a base_size argument that scales all text — titles, axis labels, tick labels, legend — proportionally. Bump it up for slides or print, drop it for dense dashboards. Pair it with base_family to set the font.
Rather than adding a theme to every plot, set a default once with theme_set(). Every subsequent plot uses it until you change it back. Save the old theme first so you can restore it.

Once you find a look you like, wrap it in a function so you can reuse it everywhere. Start from a base theme and override a few elements with %+replace%, which replaces whole elements cleanly. This is the pattern behind a house theme like the future theme_datanovia().
library(ggplot2)
theme_house <- function(base_size = 12) {
theme_minimal(base_size = base_size) %+replace%
theme(
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank(),
axis.title = element_text(face = "bold", colour = "#3a86d4"),
plot.background = element_rect(fill = "white", colour = NA)
)
}
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme_house()
To save a plot with no background — for slides or overlays — blank the panel, plot, and legend rectangles to "transparent" (or use theme_void() for no chrome at all), then tell ggsave() to keep transparency with bg = "transparent".
library(ggplot2)
p <- ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme_minimal() +
theme(
panel.background = element_rect(fill = "transparent", colour = NA),
plot.background = element_rect(fill = "transparent", colour = NA),
legend.background = element_rect(fill = "transparent", colour = NA)
)
p
For control over the saved image’s dimensions and resolution, see the saving plots guide.
For a dark dashboard look, hand-build a black panel and plot background, then switch the text and grid to a light colour so it stays readable. theme_dark() gives a grey-dark starting point; this goes fully black.
library(ggplot2)
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
geom_boxplot(fill = "#3a86d4", colour = "white", alpha = 0.85) +
labs(x = "Dose (mg/day)", y = "Tooth length") +
theme(
plot.background = element_rect(fill = "black", colour = NA),
panel.background = element_rect(fill = "black", colour = NA),
panel.grid.major = element_line(colour = "grey25"),
panel.grid.minor = element_blank(),
axis.text = element_text(colour = "white"),
axis.title = element_text(colour = "white")
)
The plots above were rendered at build time. Want to experiment? Edit the code and press Run — it executes in your browser via webR (no server, no install).
Working in Python? A matplotlib styling and stylesheets guide is coming to the Python series.
Ask Prova “rewrite my theme() call so the plot has a white background and no grid lines” — it answers with code you can run, so the styling is reproducible. The runtime is the judge. Ask Prova →
The grey panel comes from the default theme_gray(). Switch to a theme that is already white — add theme_bw() or theme_classic() as a layer — or keep your theme and blank just the panel with theme(panel.background = element_blank()).
The panel is the rectangle directly behind your data, set with panel.background; the plot background fills the whole image including the margins around the panel, set with plot.background. They are styled separately, so colouring one does not affect the other.
Every built-in theme takes a base_size argument that scales all text proportionally — titles, axis labels, tick labels, and legend — at once, e.g. theme_minimal(base_size = 18). Pair it with base_family to also set the font.
Call theme_set() once, e.g. theme_set(theme_bw()), and every subsequent plot uses it without a per-plot theme layer. Save the current theme first with old <- theme_set(...) so you can restore it later with theme_set(old).
Set the panel, plot, and legend rectangles to "transparent" with element_rect(fill = "transparent", colour = NA) inside theme(), then keep the transparency on disk by passing bg = "transparent" to ggsave(). For no chrome at all, start from theme_void().
Complete the code so the plot has a clean white background instead of the default grey panel.
You restyled a ggplot2 plot by swapping built-in themes, removed the default grey background for a clean white one, set the panel and plot background colours and grid lines, used the four element_*() helpers, scaled all text with base_size, set a session-wide default with theme_set(), and wrapped your own look in a reusable custom theme. Styling the data colours (palettes, scale_fill_*) lives in the colours guide, and legend placement and styling in the legend guide. Next, apply these themes to the rest of the ggplot2 grammar.
Prefer a book? GGPlot2 Essentials is available as a downloadable PDF — every lesson in this series, offline and yours to keep.
Prove you can do it. Master the whole ggplot2 series — track your path, build projects, and earn a certificate.
Go Pro — unlimited Prova on your own data and a verifiable certificate that proves the skill.
from $15/mo billed yearly
✓ You're Pro — keep going. The runtime is the judge.
Ready to level up?
Get new R & Python lessons by email
Practical, reproducible, no spam. Unsubscribe anytime.
Double opt-in. We never share your email.
This lesson is reproducible: the figures are executed at build time (if they render, the code works), and the sandbox + quiz re-run live in your browser. The runtime is the judge.
@online{2026,
author = {},
title = {Ggplot2 {Themes} in {R:} {Customize} the {Look}},
date = {2026-06-20},
url = {https://www.datanovia.com/learn/data-visualization/ggplot2/themes},
langid = {en}
}