ggplot2 theme() in R: Customize Every Plot Element
The theme() function in ggplot2 controls every non-data element of a plot: fonts, axes, legends, gridlines, backgrounds, and panels. It accepts named arguments paired with element_text(), element_line(), element_rect(), or element_blank() helpers.
theme(plot.title = element_text(size = 16, face = "bold")) # title styling theme(axis.text.x = element_text(angle = 45, hjust = 1)) # rotate x labels theme(legend.position = "top") # move legend theme(panel.grid.minor = element_blank()) # remove minor grid theme(panel.background = element_rect(fill = "white")) # change background theme(axis.line = element_line(color = "black")) # add axis lines theme(plot.margin = margin(10, 10, 10, 10)) # adjust margins
Need explanation? Read on for examples and pitfalls.
What theme() does in ggplot2
theme() styles everything in a ggplot2 plot that is not the data itself. Titles, axis labels, tick marks, legend placement, panel backgrounds, gridlines, and margins all live inside theme().
The function takes one argument per element, and each argument expects a matching element_*() helper that describes how to draw it. For example, axis.title = element_text(size = 14) says "the axis title element should be a text element rendered at size 14." Pass element_blank() to hide an element entirely.
Theme calls layer on top of a base theme (theme_grey() by default). To start from a different base, add theme_minimal() or theme_bw() first, then call theme() to override specific elements.
theme() call can restyle dozens of plots consistently.Syntax
theme(... = element_*(...)). Each argument names an element, each value describes how to render it.
The default look comes from theme_grey(), ggplot2's built-in starting point.
Seven common theme() patterns
1. Style the title and subtitle
element_text() takes size, face ("plain", "bold", "italic", "bold.italic"), color, family, angle, hjust, and vjust.
2. Rotate x-axis labels
hjust = 1 when rotating axis labels. Without it, labels collide with the axis. With it, the right edge of each label aligns with the tick.3. Move and restyle the legend
legend.position accepts "top", "bottom", "left", "right", "none", or a numeric vector like c(0.8, 0.2) for inside-the-plot placement.
4. Remove gridlines
element_blank() removes an element entirely. Use it for gridlines, axis ticks, or anything you want to hide without affecting layout.
5. Change the panel background
element_rect() controls filled rectangles. Always set fill = NA on borders, otherwise the rectangle covers the data.
6. Add axis lines
element_line() controls line elements: axis lines, gridlines, and tick marks. Arguments include color, linewidth, linetype, and lineend.
7. Adjust plot margins
margin(top, right, bottom, left) sets outer plot spacing in points. Use it when titles get clipped or plots crowd the page edge.
theme() calls are order-sensitive. Later calls overwrite earlier ones on the SAME element. Combine all overrides into one theme() call, or place theme() AFTER theme_minimal() so your overrides win.theme() vs theme_minimal() vs theme_classic() vs theme_bw()
theme() is the low-level customizer; theme_*() functions are pre-built bundles. Apply a theme_*() first for a complete look, then layer theme() on top to fine-tune.
| Function | Background | Gridlines | Best for |
|---|---|---|---|
theme_grey() |
Grey | White major + minor | ggplot2 default |
theme_minimal() |
White | Light grey, no borders | Reports, dashboards |
theme_classic() |
White | None, axis lines only | Scientific papers |
theme_bw() |
White | Light grey, full border | Publication figures |
theme_light() |
White | Light grey, light border | Slides, presentations |
theme_void() |
White | None, no axes | Maps, sparklines |
Combine a base theme with theme() overrides for the cleanest result:
The four element_*() helpers
Every theme() argument expects one of four element_*() helpers, or element_blank() to hide the element. Pick the helper that matches the element type: text, line, or filled rectangle.
| Helper | Controls | Common arguments |
|---|---|---|
element_text() |
Text elements (titles, labels, ticks) | size, face, color, family, angle, hjust, vjust |
element_line() |
Line elements (axes, grids, ticks) | color, linewidth, linetype, lineend |
element_rect() |
Filled rectangles (backgrounds, borders) | fill, color, linewidth, linetype |
element_blank() |
Hide the element completely | none |
element_blank() removes an element from rendering, but the layout space MAY still be reserved. For axis titles and ticks, use element_blank() to fully reclaim the space. For panel borders, the gap stays unless you also drop the background.Build a reusable custom theme
Wrap your theme() overrides in a function for consistent branding across many plots. Call the function on every chart to inherit the same look.
Now every plot can call theme_brand() and inherit the same look. Pass base_size to scale everything for slides vs reports.
Common pitfalls
Pitfall 1: Calling theme() before the base theme. Putting theme(plot.title = ...) BEFORE theme_minimal() lets theme_minimal() overwrite your changes. Always put theme() last.
Pitfall 2: Forgetting fill = NA on element_rect() for borders. Without it, the rectangle fills the entire panel and hides your data.
Pitfall 3: Wrong element name. axis.title.x exists; axis.title.X does not. Element names are case-sensitive and lowercase. Use ?theme to see the full list.
Pitfall 4: Using element_blank() when you want transparent. element_blank() removes the element; for a transparent fill, use element_rect(fill = "transparent", color = NA).
Try it yourself
Try it: Apply theme_minimal() to the mtcars scatter plot, then make the title bold and remove all minor gridlines. Save to ex_plot.
Click to reveal solution
Explanation: theme_minimal() sets the base look; the subsequent theme() overrides the title face and removes the minor grid. Always place custom theme() after the base.
Related ggplot2 functions
After mastering theme(), explore these companions:
theme_minimal(),theme_classic(),theme_bw(): pre-built theme bundleselement_text(),element_line(),element_rect(),element_blank(): element constructorslabs(): control plot labels (title, subtitle, caption, axis names)guides(): customize legend guides (colorbar, key labels)margin(): set plot, panel, and text marginsggthemespackage: extra theme bundles (theme_economist, theme_wsj, theme_fivethirtyeight)
FAQ
What is the difference between theme() and theme_minimal()?
theme() is the low-level customizer that changes individual elements. theme_minimal() is a complete pre-built theme bundle. You typically apply a base like theme_minimal() first, then layer theme() on top to override specific elements (title, legend position, gridlines).
How do I remove gridlines in ggplot2?
Use theme(panel.grid = element_blank()) to remove ALL gridlines, or target specific ones: panel.grid.major.x, panel.grid.minor.y, etc. Combine with axis.line = element_line() if you want axis lines to remain visible.
Why does my theme() get ignored?
Two common causes: (1) you placed theme() BEFORE a theme_*() call, so the base theme overwrote your changes; (2) the element name is misspelled or capitalized incorrectly. Run ?theme for the full canonical element list.
Can I save a custom theme for reuse?
Yes. Wrap your theme_minimal() + theme(...) block in an R function that returns the combined theme object. Apply it with + theme_brand() to any plot, just like a built-in theme. This is the standard pattern for organizations enforcing brand-consistent figures.
How do I change font in ggplot2 theme?
Use the family argument inside element_text(): theme(text = element_text(family = "Arial")). To use a custom font, register it first with extrafont::font_import() or showtext::font_add_google(), otherwise R falls back to its default sans-serif.