Creating a custom ggplot2 theme

R
Visualisation
Author

Måns Thulin

Published

May 21, 2025

Every year, I introduce hundreds of people to R, both in my live online courses and in university courses. In most of these, we use ggplot2, and invariably someone will raise their hand and ask whether it is possible to change the grey background to a different colour.

And it is! In this blog post, I’ll describe how to create a custom ggplot2 theme. In an upcoming post, I’ll then explain how to put it in an R package together with colour palettes, making your theme and palettes the default in ggplot2.

Changing the defaults

For this example, we’ll plot wind speed and temperature from the airquality data:

head(airquality)
  Ozone Solar.R Wind Temp Month Day
1    41     190  7.4   67     5   1
2    36     118  8.0   72     5   2
3    12     149 12.6   74     5   3
4    18     313 11.5   62     5   4
5    NA      NA 14.3   56     5   5
6    28      NA 14.9   66     5   6

The default theme has a grey background:

library(tidyverse)
ggplot(airquality, aes(Wind, Temp, colour = factor(Month))) + geom_point()

We can change the background colour, the grid, the font family, and all sorts of other settings using the theme function. Below is a rather extreme example, where we change many things at once; not because I recommend making all these changes, but just to see what is possible. I definitely don’t recommend using this particular combinations of colours - they were chosen to (hopefully) make it easier to see which part of the code affects which element of the plot:

ggplot(airquality, aes(Wind, Temp, colour = factor(Month))) +
    geom_point() +
     theme(
          # Change the background colour(s) and the border colour:
          panel.background = element_rect(fill = "pink",
                                          colour = "red",
                                          linewidth = 2),
          plot.background = element_rect(fill = "darkblue"),
          legend.background = element_rect(fill = "white"),
          # Change the colours of the grid lines:
          panel.grid.major = element_line(colour = "cyan"),
          panel.grid.minor = element_line(colour = "yellow2",
                                          linetype = "dotted",
                                          linewidth = 1),
          # Change fonts, font size, rotation, and colour of the text on the axis:
          axis.text = element_text(family = "Courier New",
                                   colour = "magenta",
                                   face = "bold",
                                   angle = 45,
                                   hjust = 1),
          axis.title = element_text(family = "Arial",
                                    colour = "skyblue",
                                    face = "italic",
                                    size = 18)
          )

Creating a theme function

We can create a custom function with all of the above, which can then be added to our plots. For this, I’ll also add settings for text, which control the base size of text in the plot.

my_theme <- function(size = 10) {
    theme(
          # Set base text size:
          text = element_text(size = size),
          # Change the background colour(s) and the border colour:
          panel.background = element_rect(fill = "pink",
                                          colour = "red",
                                          linewidth = 2),
          plot.background = element_rect(fill = "darkblue"),
          legend.background = element_rect(fill = "white"),
          # Change the colours of the grid lines:
          panel.grid.major = element_line(colour = "cyan"),
          panel.grid.minor = element_line(colour = "yellow2",
                                          linetype = "dotted",
                                          linewidth = 1),
          # Change fonts, font size, rotation, and colour of the text on the axis:
          axis.text = element_text(family = "Courier New",
                                   colour = "magenta",
                                   face = "bold",
                                   angle = 45,
                                   hjust = 1),
          axis.title = element_text(family = "Arial",
                                    colour = "skyblue",
                                    face = "italic",
                                    size = 1.5*size)
          )
}

We can now apply it to our plot, using different base font sizes:

ggplot(airquality, aes(Wind, Temp, colour = factor(Month))) +
    geom_point() +
    my_theme(size = 8)

ggplot(airquality, aes(Wind, Temp, colour = factor(Month))) +
    geom_point() +
    my_theme(size = 14)

A simple and clean theme

Below is an example that is similar to a theme I created for a client who wanted a very clean look for their plots. Compared to the previous example, it uses some additional settings, like axis.ticks.x that controls the ticks on the x axis. element_blank() is used to remove things, e.g. the axis ticks, completely from the theme. The theme function has two arguments: size (base font size) and angle (rotation of axis text):

custom_theme <- function(size = 10, angle = 0)  {
  theme(legend.position = "bottom",
                 axis.title.x = element_blank(),
                 axis.title.y = element_blank(),
                 legend.title  = element_blank(),
                 axis.ticks.y.left = element_blank(),
                 axis.ticks.x = element_blank(),
                 text = element_text(colour = "black",
                                     size = size),
                 axis.text.x = element_text(angle = angle,
                                            hjust = ifelse(angle == 0, 0.5, 1)),
                 plot.background = element_rect(fill = "white"),
                 panel.background = element_rect(fill = "white"),
                 legend.background = element_rect(fill = "white"),
                 legend.key = element_rect(fill = "white"),
                 panel.grid.major.x = element_line(color = "#8d8d8c",
                                                   linewidth = 0.1,
                                                   linetype = "dotted"),
                 panel.grid.major.y = element_line(color = "#8d8d8c",
                                                   linewidth = 0.1)
        )
}

Let’s try it on our plot:

ggplot(airquality, aes(Wind, Temp, colour = factor(Month))) +
    geom_point() +
    custom_theme(size = 12, angle = 90)

To make this the default theme in the current R session, use the theme_set function:

theme_set(custom_theme())

It will then be applied automatically to all plots.

That’s all for now. In an upcoming post, we’ll see how to put this in a package, along with custom palettes and other goodies.