How to Change Themes in ggplot2
Themes in ggplot2 control every non-data visual element of your plots: fonts, colors, grid lines, backgrounds, axis styling, legend positioning, and more. While your data and geometric layers...
Key Insights
- ggplot2 themes control all non-data visual elements of your plots—applying a complete theme like
theme_minimal()ortheme_classic()instantly transforms your visualization’s appearance with a single line of code - The
theme()function provides granular control over 40+ visual elements through four element types:element_text(),element_line(),element_rect(), andelement_blank(), letting you customize everything from axis labels to panel backgrounds - Create reusable custom themes by combining theme modifications and use
theme_set()to apply them globally across all plots in your session, ensuring visual consistency across your entire analysis
Introduction to ggplot2 Themes
Themes in ggplot2 control every non-data visual element of your plots: fonts, colors, grid lines, backgrounds, axis styling, legend positioning, and more. While your data and geometric layers determine what information appears, themes determine how that information looks. The difference between a default ggplot2 visualization and a publication-ready figure often comes down to thoughtful theme customization.
Understanding themes is essential because default settings rarely match your needs. Academic journals have specific formatting requirements. Corporate presentations demand brand consistency. Data dashboards need clean, distraction-free designs. Mastering themes lets you meet these requirements without recreating plots from scratch.
Here’s a basic scatter plot with ggplot2’s default theme:
library(ggplot2)
library(dplyr)
# Create sample data
set.seed(42)
data <- data.frame(
x = rnorm(100),
y = rnorm(100),
category = sample(c("A", "B", "C"), 100, replace = TRUE)
)
# Basic plot with default theme
p <- ggplot(data, aes(x = x, y = y, color = category)) +
geom_point(size = 3, alpha = 0.7) +
labs(
title = "Sample Scatter Plot",
x = "X Variable",
y = "Y Variable"
)
print(p)
This default theme (theme_gray()) features a gray background with white grid lines—a design choice that helps distinguish the plot area but may not suit all contexts.
Using Built-in Complete Themes
ggplot2 includes eight complete themes that cover most common use cases. These themes apply coordinated changes across all visual elements with a single function call. The most frequently used are:
theme_minimal(): Clean, modern look with minimal visual elementstheme_classic(): Traditional publication style with axis lines, no gridtheme_bw(): Black and white theme with panel borderstheme_light(): Light gray lines and axestheme_dark(): Dark background for presentations
Here’s how different complete themes transform the same plot:
library(gridExtra)
# Create plots with different themes
p_minimal <- p + theme_minimal() + ggtitle("theme_minimal()")
p_classic <- p + theme_classic() + ggtitle("theme_classic()")
p_bw <- p + theme_bw() + ggtitle("theme_bw()")
p_dark <- p + theme_dark() + ggtitle("theme_dark()")
# Display side by side
grid.arrange(p_minimal, p_classic, p_bw, p_dark, ncol = 2)
Each theme makes opinionated choices about what to emphasize. theme_classic() removes grid lines entirely, focusing attention on the data and axes. theme_minimal() keeps subtle grid lines for reference while eliminating background colors. theme_dark() inverts the color scheme for better visibility in dark environments or presentations.
Choose your complete theme based on context: theme_minimal() for modern dashboards, theme_classic() for academic publications, theme_dark() for slides with dark backgrounds.
Customizing Themes with theme() Function
Complete themes provide excellent starting points, but you’ll often need granular control. The theme() function lets you modify individual elements without rebuilding everything from scratch.
The most commonly customized elements include:
- Text elements:
plot.title,axis.title,axis.text,legend.text - Panel elements:
panel.grid,panel.background,panel.border - Legend elements:
legend.position,legend.background,legend.key - Plot elements:
plot.background,plot.margin
Here’s how to customize specific theme elements:
p_custom <- p +
theme_minimal() +
theme(
# Customize title
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
# Remove minor grid lines
panel.grid.minor = element_blank(),
# Customize major grid lines
panel.grid.major = element_line(color = "gray90", linewidth = 0.5),
# Move legend to bottom
legend.position = "bottom",
# Customize axis text
axis.text = element_text(size = 11),
axis.title = element_text(size = 12, face = "bold")
)
print(p_custom)
This approach—starting with a complete theme and modifying specific elements—is more maintainable than building themes from scratch. You inherit sensible defaults while overriding only what needs to change.
Creating and Saving Custom Themes
When you need consistent styling across multiple plots, create a custom theme function. This encapsulates your design decisions and makes them reusable.
Here’s a custom corporate theme:
theme_corporate <- function(base_size = 12) {
theme_minimal(base_size = base_size) +
theme(
# Typography
text = element_text(family = "sans", color = "#2C3E50"),
plot.title = element_text(size = base_size * 1.4, face = "bold",
hjust = 0, margin = margin(b = 10)),
plot.subtitle = element_text(size = base_size * 1.1,
margin = margin(b = 15)),
# Grid and background
panel.grid.minor = element_blank(),
panel.grid.major = element_line(color = "#E8E8E8", linewidth = 0.3),
plot.background = element_rect(fill = "white", color = NA),
panel.background = element_rect(fill = "white", color = NA),
# Legend
legend.position = "top",
legend.title = element_text(size = base_size * 0.9, face = "bold"),
legend.text = element_text(size = base_size * 0.9),
legend.key = element_blank(),
# Margins
plot.margin = margin(20, 20, 20, 20)
)
}
# Apply to a plot
p_corporate <- p +
theme_corporate() +
labs(subtitle = "Demonstrating custom corporate theme")
print(p_corporate)
To apply your custom theme to all subsequent plots in your session, use theme_set():
theme_set(theme_corporate())
# Now all plots use the corporate theme by default
ggplot(data, aes(x = category, y = y, fill = category)) +
geom_boxplot() +
labs(title = "Boxplot with Corporate Theme")
This is particularly valuable in R Markdown documents or Shiny applications where you create dozens of plots that should share a consistent appearance.
Working with Theme Elements
ggplot2 themes rely on four element functions that control different visual properties:
element_text() controls all text elements:
p + theme(
axis.title = element_text(
family = "sans",
size = 14,
face = "bold",
color = "#2C3E50",
angle = 0,
hjust = 0.5,
vjust = 0.5
)
)
element_line() controls lines (grid lines, axis lines, borders):
p + theme(
panel.grid.major = element_line(
color = "gray70",
linewidth = 0.5,
linetype = "dashed"
),
axis.line = element_line(
color = "black",
linewidth = 1
)
)
element_rect() controls rectangular regions (backgrounds, borders):
p + theme(
plot.background = element_rect(
fill = "#F5F5F5",
color = "black",
linewidth = 1
),
panel.background = element_rect(
fill = "white",
color = NA
),
legend.background = element_rect(
fill = "white",
color = "gray50",
linewidth = 0.5
)
)
element_blank() removes elements entirely:
p + theme(
panel.grid = element_blank(),
axis.line.y = element_blank(),
legend.title = element_blank()
)
Here’s a comprehensive example using all element types:
p_detailed <- p +
theme_minimal() +
theme(
# Text elements
plot.title = element_text(size = 18, face = "bold", color = "#1A1A1A"),
axis.text.x = element_text(angle = 45, hjust = 1, size = 10),
# Line elements
panel.grid.major.x = element_line(color = "gray80", linewidth = 0.3),
panel.grid.major.y = element_line(color = "gray80", linewidth = 0.3),
axis.line = element_line(color = "black", linewidth = 0.5),
# Rectangle elements
plot.background = element_rect(fill = "#FAFAFA", color = NA),
panel.background = element_rect(fill = "white", color = "gray70", linewidth = 0.5),
legend.background = element_rect(fill = "white", color = "gray50"),
# Blank elements
panel.grid.minor = element_blank()
)
print(p_detailed)
Best Practices and Tips
Layer themes correctly: When combining themes, order matters. Apply complete themes first, then customizations:
# Correct: customizations override complete theme
p + theme_minimal() + theme(legend.position = "bottom")
# Wrong: complete theme overrides your customizations
p + theme(legend.position = "bottom") + theme_minimal()
Use %+replace% for theme inheritance: When building custom themes that extend existing ones, use %+replace% to replace elements rather than adding to them:
theme_custom <- theme_minimal() %+replace%
theme(
panel.grid = element_blank() # Replaces all grid settings
)
Save and load themes: Store custom themes in separate R files and source them across projects:
# In themes.R
theme_corporate <- function() { ... }
# In analysis.R
source("themes.R")
theme_set(theme_corporate())
Export with consistent sizing: When saving plots, specify dimensions to ensure theme elements scale appropriately:
ggsave(
"plot.png",
plot = p_corporate,
width = 8,
height = 6,
dpi = 300,
bg = "white"
)
Test themes with different plot types: Your custom theme should work well with various geometries—points, lines, bars, facets. Test thoroughly:
# Test with different plot types
plots <- list(
ggplot(data, aes(x, y)) + geom_point(),
ggplot(data, aes(category, y)) + geom_boxplot(),
ggplot(data, aes(x)) + geom_histogram()
)
lapply(plots, function(p) p + theme_corporate())
Themes transform ggplot2 from a functional plotting tool into a professional visualization system. Master the built-in themes for quick styling, use theme() for targeted adjustments, and create custom theme functions for consistent, branded visualizations across your entire workflow.