Written and maintained by CASRAI Editorial Board
Published
On this page
In R data visualization, the theme system in ggplot2 controls all non-data visual elements of a plot, including typography, axis lines, tick marks, gridlines, legends, facet labels, and background colors. While aesthetic mappings (aes()) and geometric layers (geom_*()) govern how data values translate into visual coordinates and shapes, the theme system governs how the non-data canvas is displayed. For academic and industry researchers, configuring the theme in R is essential to transform exploratory charts into publication-ready figures that comply with strict journal formatting requirements, readability guidelines, and institutional style guides.
What it is used for in research
Scientific journals, funding agencies, and conference proceedings enforce precise graphical standards. Submitting figures with default software aesthetics—such as the grey background panel and default font sizing in standard R plots—can result in editorial revision requests or illegible print reproductions. In research workflows, the theme system in R serves several critical functions:
- Publication compliance: Converting default exploratory visualizations into high-contrast figures suitable for print and digital publication. Leading journals such as Nature, Science, and PLOS ONE typically mandate white backgrounds, sans-serif typography (such as Arial or Helvetica) between 6 pt and 10 pt, minimum line weights (≥ 0.5 pt), and uncluttered coordinate areas.
- Hierarchical element inheritance: Establishing consistent design rules across an entire project. In
ggplot2, theme elements follow a hierarchical inheritance structure: broad styling defined at the top-leveltextorlinelevel cascades down toaxis.text,plot.title, andpanel.grid, minimizing repetitive manual overrides. - Multi-figure layout harmony: Synchronizing graphical styling across multi-panel figures. When combining multiple subplots into composite layouts using packages such as patchwork in R, standardized themes prevent jarring discrepancies in font sizing, margin offsets, and axis tick alignments.
- Analytical reporting and reproducible research: Embedding theme definitions into statistical scripts allows data pipelines to automatically generate consistent figures whenever raw data updates. When publishing code alongside open data repositories, scripted themes guarantee that figures can be reproduced exactly without manual post-processing in external vector illustration software.
- Integration with specialized analytical packages: The theme system interfaces directly with downstream analytical packages, whether styling regression plots from modeling in R, phylogenetic trees in ggtree, or animated visual sequences created with animation in R.
Install or access
The primary theme system in R is part of the ggplot2 package, developed by Hadley Wickham, Winston Chang, Lionel Henry, Thomas Lin Pedersen, and the tidyverse development team. The package is hosted on the Comprehensive R Archive Network (CRAN) under the open-source MIT license.
To install ggplot2 from an active R session (version 3.5.0 or later), run the standard CRAN package manager command:
# Install ggplot2 directly from CRAN
install.packages("ggplot2")
# Alternatively, install the complete tidyverse ecosystem
install.packages("tidyverse")
Once installed, load the library into your workspace:
library(ggplot2)
The core functions documented here—including theme(), theme_set(), theme_get(), theme_bw(), theme_classic(), element_text(), element_line(), element_rect(), and element_blank()—are natively exported by ggplot2 and do not require external graphical drivers or proprietary licenses.
Step-by-step task
The following five-step workflow demonstrates how to take a baseline statistical visualization, strip away exploratory styling, construct a reusable custom academic theme, and enforce it session-wide.
Step 1: Inspect the default theme
By default, ggplot2 applies theme_grey() (also written as theme_gray()), which features a signature light-grey panel background and white major/minor gridlines. You can inspect the current active theme configuration programmatically using theme_get():
# Retrieve the active session theme
active_theme <- theme_get()
# Inspect specific element definitions
active_theme$plot.title
active_theme$axis.text
Step 2: Choose an appropriate complete base theme
Rather than customizing every single graphical element from a blank canvas, research workflows begin by selecting a pre-packaged complete theme (ggtheme). Complete themes reset all non-data parameters to a cohesive visual standard:
theme_bw(): Replaces the grey panel with a clean white background and retains subtle grey gridlines with a black outer border. Ideal for general lab reporting.theme_classic(): Displays distinct x and y axis lines with tick marks, removing all gridlines and outer borders. Mimics traditional scientific and biomedical manuscript charts.theme_light(): Employs very light grey lines for borders and grids, placing maximum visual emphasis on data points.theme_linedraw(): Features crisp black lines of varying weights on a white background, useful for high-contrast technical diagrams.theme_minimal(): Eliminates panel borders entirely, preserving only faint gridlines.theme_void(): Completely strips all axes, labels, and guides; used primarily for network topologies and geographic coordinate maps.
# Generate a baseline scatter plot using built-in data
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
geom_point(size = 2.5, alpha = 0.85) +
labs(
title = "Vehicle Fuel Economy vs. Weight",
subtitle = "Stratified by Engine Cylinder Count",
x = "Weight (1,000 lbs)",
y = "Miles Per Gallon (mpg)",
color = "Cylinders"
)
# Apply a complete academic base theme
p_classic <- p + theme_classic(base_size = 11, base_family = "sans")
Step 3: Modify specific elements with element functions
To adjust individual components of a plot, add the theme() function. Every argument in theme() corresponds to a specific visual component and expects a dedicated element constructor:
element_text(): Controls font family, face (plain, italic, bold), color, size (in points), horizontal alignment (hjust), vertical alignment (vjust), angle, and margins.element_line(): Controls line color, linewidth, linetype (solid, dashed, dotted), and line ends.element_rect(): Controls borders and fills for rectangular regions (panel, plot canvas, legend boxes).element_blank(): Completely suppresses drawing of the target component and releases its allocated spacing.
# Fine-tune non-data elements for journal submission
p_tuned <- p_classic +
theme(
# Typography: bold left-aligned title with vertical breathing room
plot.title = element_text(face = "bold", size = 12, hjust = 0, margin = margin(b = 4, unit = "pt")),
plot.subtitle = element_text(size = 10, colour = "grey30", margin = margin(b = 8, unit = "pt")),
# Axes: crisp labels, grey tick marks, clear separation
axis.title = element_text(face = "bold", size = 10),
axis.text = element_text(size = 9, colour = "grey20"),
axis.line = element_line(colour = "black", linewidth = 0.5),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
# Legend: positioned inside or below to maximize plot canvas
legend.position = "bottom",
legend.title = element_text(face = "bold", size = 9),
legend.text = element_text(size = 8.5),
legend.background = element_rect(fill = "transparent", colour = NA),
legend.key = element_rect(fill = "transparent", colour = NA),
# Canvas margins (top, right, bottom, left)
plot.margin = margin(t = 10, r = 10, b = 10, l = 10, unit = "pt")
)
Step 4: Build a reusable custom theme function
Copying and pasting long theme() blocks across multiple analysis scripts introduces errors and complicates updates when journal specifications change. The best practice is to encapsulate your styling into a standalone function. When writing custom themes, use the %+replace% operator instead of +. The %+replace% operator replaces the specified theme elements entirely rather than merging them, preventing unwanted aesthetic properties from leaking into the new theme:
# Define a reusable publication theme function
theme_manuscript <- function(base_size = 10, base_family = "sans") {
# Start from a clean classic foundation
theme_classic(base_size = base_size, base_family = base_family) %+replace%
theme(
# Typography
plot.title = element_text(
face = "bold",
size = base_size * 1.2,
hjust = 0,
margin = margin(b = 4, unit = "pt")
),
plot.subtitle = element_text(
size = base_size,
colour = "grey35",
hjust = 0,
margin = margin(b = 6, unit = "pt")
),
plot.caption = element_text(
size = base_size * 0.8,
colour = "grey40",
hjust = 1,
margin = margin(t = 4, unit = "pt")
),
# Axis formatting
axis.title = element_text(face = "bold", size = base_size),
axis.text = element_text(size = base_size * 0.9, colour = "grey15"),
axis.line = element_line(colour = "black", linewidth = 0.45),
axis.ticks = element_line(colour = "black", linewidth = 0.4),
# Legend formatting
legend.position = "bottom",
legend.direction = "horizontal",
legend.title = element_text(face = "bold", size = base_size * 0.9),
legend.text = element_text(size = base_size * 0.85),
legend.background = element_blank(),
legend.key = element_blank(),
# Plot canvas spacing
plot.margin = margin(t = 8, r = 8, b = 8, l = 8, unit = "pt")
)
}
Step 5: Enforce global session defaults
To apply your custom theme across dozens of figures without appending + theme_manuscript() to each plot call, set the active theme globally using theme_set(). Always store the prior theme object so you can restore default behavior at the end of the script:
# Save existing theme and apply custom theme globally
previous_theme <- theme_set(theme_manuscript(base_size = 10))
# All subsequent ggplot objects automatically adopt theme_manuscript
p1 <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()
p2 <- ggplot(mtcars, aes(x = factor(cyl), y = hp)) + geom_boxplot()
# Restore session defaults when complete
theme_set(previous_theme)
Output and how to read it
In R, a ggplot theme object is a structured S3 list of class c("theme", "gg") containing over 90 named properties. Understanding how R evaluates and renders these objects ensures proper layout debugging:
- Element inheritance resolution: During rendering,
ggplot2traverses the inheritance tree. For example, if you modifytext = element_text(colour = "navy"), any unassigned text sub-element—such asaxis.text.x,legend.title, andstrip.text—automatically inherits the navy colour. If you subsequently assignaxis.text.x = element_text(colour = "black"), that specific node overrides the inherited value without affecting the parent or sibling nodes. - Relative sizing: Functions like
rel()allow subordinate elements to scale proportionately withbase_size. Intheme_manuscript(), specifyingsize = rel(0.85)ensures that if you adjust the overall plot frombase_size = 10tobase_size = 14for a poster presentation, tick labels and captions scale proportionally. - Coordinate canvas allocation: The
plot.marginsetting defines the outer boundary spacing between the coordinate panel and the canvas edge. If axis tick labels are rotated (e.g., long taxon or gene names) and appear truncated in the output, increasing the bottom margin allocates the necessary rendering space. - Export validation: When saving formatted figures for journal submission using
ggsave(), specify physical dimensions and resolution explicitly to preserve theme proportions:# Save publication figure at 300 DPI in target print dimensions ggsave( filename = "figure_1.pdf", plot = p_tuned, width = 85, # Standard single-column width in mm height = 70, units = "mm", dpi = 300 )
Troubleshooting
The following table addresses common technical obstacles encountered when configuring themes in R for scientific publication:
| Issue | Underlying Cause | Resolution |
|---|---|---|
Custom theme() overrides have no effect |
Theme additions are order-dependent. Adding a complete theme (such as + theme_bw()) after your theme() call overwrites all previous element customizations. |
Always declare the complete theme first, followed by granular theme(...) adjustments: p + theme_bw() + theme(axis.text = ...). |
| Axis tick labels clipped at canvas edge | Long category names or angled tick labels extend beyond the allocated canvas boundaries. | Adjust the label angle and justification using axis.text.x = element_text(angle = 45, hjust = 1), and increase outer canvas margins with plot.margin = margin(t = 10, r = 10, b = 25, l = 10, unit = "pt"). |
%+replace% causes missing element error |
The %+replace% operator completely drops element inheritance. If sub-elements are assigned without basic structural attributes, rendering fails. |
Start the replacement chain from an established complete theme (e.g., theme_classic() %+replace% theme(...)) rather than constructing an empty theme() object. |
| Font family rendering failure in PDF exports | The requested font family (e.g., Helvetica, Times New Roman) is not embedded or recognized by the active graphics device. | Use cross-platform standard font names ("sans", "serif", "mono") or integrate the showtext or extrafont packages to embed TrueType/OpenType fonts into PDF output devices. |
| Facet strip styling does not match panel | Facet strip titles and background rectangles are governed by separate parameters (strip.text and strip.background) rather than panel.background. |
Explicitly configure strip.background = element_rect(fill = "grey95", colour = "black", linewidth = 0.4) and strip.text = element_text(face = "bold", size = 9) in your theme call. |
Free alternatives
While ggplot2 is the predominant visualization library in R, researchers can choose among several open-source alternatives and extension libraries depending on analytical workflows:
| Software / Package | Styling Paradigm | License | Key Research Strengths | Limitations |
|---|---|---|---|---|
ggplot2::theme() |
Grammar of Graphics modular element system with hierarchical inheritance | MIT | Extremely flexible; precise programmatic control; ubiquitous in academic publishing; extensive package ecosystem | Requires understanding element constructors; steep initial learning curve for fine typography |
Base R Graphics (par()) |
Global parameter list governing canvas margins, axes, and font scales | GPL-2 | GPL-3 | Zero external dependencies; bundled with base R; exceptionally fast execution on large numeric vectors | No object-oriented plot persistence; complex multi-panel coordination; rigid coordinate clipping |
lattice |
Trellis graphics display system configured via trellis.par.set() |
GPL-2 | GPL-3 | Built-in conditioned multivariate plots; consistent panel strip layouts; fast rendering of high-dimensional data | Less intuitive syntax than ggplot2; smaller community theme library; difficult fine-grain annotation |
ggthemes |
Pre-configured complete themes emulating major scientific and media publications | GPL-2 | Instant application of styles (e.g., theme_tufte(), theme_economist()); includes matching color palettes |
External package dependency; some media styles unsuitable for formal peer-reviewed manuscripts |
hrbrthemes |
Typography-focused ggplot2 extension emphasizing distinct micro-typography | MIT | Optimized default font scaling; refined axis line offsets; elegant publication aesthetics | Requires specific fonts (e.g., Roboto Condensed) installed on host operating system |
FAQ
What is the difference between theme() and theme_set() in R?
The theme() function modifies non-data display elements for a single plot object when appended with the + operator. In contrast, theme_set() applies a complete theme globally across the entire R session, ensuring that all subsequently plotted ggplot figures automatically inherit the specified styling without requiring individual modifications.
Why should I declare complete themes like theme_bw() before theme()?
In ggplot2, theme operations are evaluated sequentially from left to right. Complete themes such as theme_bw(), theme_classic(), and theme_minimal() define complete sets of default values for all plot elements. If you declare theme(...) customizations first and append + theme_bw() second, theme_bw() overrides your custom settings. Declaring theme_bw() first allows your subsequent theme() call to selectively overwrite specific components.
How do I remove the background grid lines in a ggplot2 figure?
To eliminate grid lines while preserving axis lines and tick marks, set the grid parameters to element_blank(): theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()). Alternatively, applying theme_classic() automatically strips all major and minor background gridlines.
What is the purpose of the %+replace% operator when defining custom themes?
The %+replace% operator replaces targeted theme elements completely, discarding any inherited attributes from the parent theme. In contrast, adding with + performs an incremental update that retains unstated parent attributes. Using %+replace% ensures that your custom theme function does not inherit unexpected aesthetic properties from previous session states.
How do I change the font family across all text elements in a ggplot2 figure?
Because of hierarchical element inheritance, you can change the global font family by adjusting the top-level text element: theme(text = element_text(family = "Arial")), or by passing the base_family argument to a complete theme constructor: theme_classic(base_family = "Arial"). All descendant text components (titles, labels, legends, tick annotations) inherit this font family automatically.
Sources
- Wickham, H., Navarro, D., and Pedersen, T. L. (2024). ggplot2: Elegant Graphics for Data Analysis (3rd ed.), Chapter 17: Themes. Accessed November 8, 2026. https://ggplot2-book.org/themes.html
- Posit Software, PBC / tidyverse. (2026). Modify components of a theme — theme. ggplot2 Reference Documentation. Accessed November 8, 2026. https://ggplot2.tidyverse.org/reference/theme.html
- Posit Software, PBC / tidyverse. (2026). Complete themes — ggtheme. ggplot2 Reference Documentation. Accessed November 8, 2026. https://ggplot2.tidyverse.org/reference/ggtheme.html
- Posit Software, PBC / tidyverse. (2026). Get, set, and modify the active theme — get_theme. ggplot2 Reference Documentation. Accessed November 8, 2026. https://ggplot2.tidyverse.org/reference/get_theme.html
- Comprehensive R Archive Network (CRAN). (2024). Package ‘ggplot2’: Create Elegant Data Visualisations Using the Grammar of Graphics (Version 3.5.1). Accessed November 8, 2026. https://cran.r-project.org/web/packages/ggplot2/







