ggplot2 Themes in R: Customize the Look

Swap themes, kill the grey background, and set panel colours — live in your browser

Data Visualization

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.

Published

June 20, 2026

Modified

July 7, 2026

TipKey takeaways
  • A theme controls every non-data element of a plot — background, grid lines, text, ticks, legend — in one place.
  • Swap the whole look with one layer: theme_minimal(), theme_bw(), theme_classic(), theme_void(), and more.
  • The default grey panel comes from theme_gray(); switch to theme_bw()/theme_classic() (or blank the panel) for a white background.
  • Fine-tune any element with theme(...) and the four helpers: element_rect() (backgrounds), element_line() (grids/ticks), element_text() (labels), element_blank() (remove).
  • The panel background sits behind the data; the plot background fills the whole image — they are set separately.
  • Scale every text label at once with base_size, e.g. theme_minimal(base_size = 16).
  • Set a theme for the whole session with theme_set(), or build your own by extending a base theme with %+replace%.
  • Every plot below is rendered for you — then tweak any example live in the sandbox.
Get the book — GGPlot2 Essentials (PDF)

Introduction

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.

Remove the grey background (get a white background)

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().

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_blank())

ggplot2 box plot of tooth length by dose with the default grey panel removed, leaving a clean white background behind the boxes.

element_blank() is the universal “remove this element” tool — it works on any theme component, not just backgrounds.

Change the panel background colour and grid lines

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)
  )

ggplot2 box plot of tooth length by dose with a pale blue panel background and white major and minor grid lines.

Change the plot background colour

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.

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(
    plot.background  = element_rect(fill = "#f0f0f0", colour = NA),
    panel.background = element_rect(fill = "white", colour = NA)
  )

ggplot2 box plot of tooth length by dose with a light grey plot background filling the margins and a white panel behind the boxes.

Remove the panel border and grid lines

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")
  )

ggplot2 box plot of tooth length by dose with all grid lines and the panel border removed, leaving only the two black axis lines.

The four theme element helpers

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
  )

ggplot2 box plot of tooth length by dose styled with all four element helpers: a pale panel rectangle, navy grid lines, bold navy axis titles, and no minor grid.

Scale all text at once with base_size

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.

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(base_size = 18)

ggplot2 box plot of tooth length by dose with theme_minimal at base_size 18, so all axis and tick text is noticeably larger.

Set a theme for the whole session

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.

library(ggplot2)
old <- theme_set(theme_bw(base_size = 13))   # make theme_bw the default

# no theme layer needed now — it is the session default
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
  geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
  labs(x = "Dose (mg/day)", y = "Tooth length")

ggplot2 box plot of tooth length by dose drawn after theme_set made theme_bw the session default, so no per-plot theme layer is needed.

theme_set(old)   # restore the previous default

Build your own custom theme

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()

ggplot2 box plot of tooth length by dose styled with a custom reusable theme: white background, light grey horizontal grid only, and bold blue axis titles.

Transparent background

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

ggplot2 box plot of tooth length by dose with the panel and plot backgrounds set to transparent, shown against the page's checkered transparency.

# to write a transparent PNG to disk, keep the background transparent on save:
# ggsave("plot.png", p, bg = "transparent")

For control over the saved image’s dimensions and resolution, see the saving plots guide.

Black / dark background plot

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")
  )

ggplot2 box plot of tooth length by dose on a black background with white axis text and faint grey grid lines for a dark-mode look.

Try it live

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.

🟢 With an AI agent

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 →

Frequently asked questions

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().

Test your understanding

Complete the code so the plot has a clean white background instead of the default grey panel.

# A single built-in theme layer does it — try theme_bw() or theme_classic().
library(ggplot2) ggplot(ToothGrowth, aes(x = factor(dose), y = len)) + geom_boxplot(fill = "#3a86d4", alpha = 0.7) + theme_bw()
library(ggplot2)
ggplot(ToothGrowth, aes(x = factor(dose), y = len)) +
  geom_boxplot(fill = "#3a86d4", alpha = 0.7) +
  theme_bw()

Conclusion

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.

Reuse

Citation

BibTeX citation:
@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}
}
For attribution, please cite this work as:
“Ggplot2 Themes in R: Customize the Look.” 2026. June 20. https://www.datanovia.com/learn/data-visualization/ggplot2/themes.