rguides

ggplot2::theme

theme() customizes every non-data visual element in ggplot2. Titles, axis labels, tick marks, panel backgrounds, grid lines, legends, strip text, and plot margins all come from theme settings. The system uses inheritance — parent settings cascade down to child elements unless you override them at a specific level.

Signature

theme(...,
      complete = FALSE,
      validate = TRUE)

... accepts named arguments for theme elements. complete = TRUE marks the theme as finished, which matters when building reusable theme functions.

Returns: A theme object (a classed list, not a string).

Element Functions

Four functions create themeable elements:

FunctionWhat it styles
element_text()Text — titles, axis labels, legend text
element_line()Lines — axis lines, grid lines, tick marks
element_rect()Rectangles — panel backgrounds, legend boxes
element_blank()Nothing — removes the element entirely
element_text(family = NULL, face = NULL, colour = NULL,
             size = NULL, hjust = NULL, vjust = NULL,
             angle = NULL, lineheight = NULL, margin = NULL,
             padding = NULL, debug = NULL)

element_line(colour = NULL, linewidth = NULL, linetype = NULL, lineend = NULL)

element_rect(fill = NULL, colour = NULL, linewidth = NULL, linetype = NULL)

Axis Styling

Axis Titles and Text

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme(
    axis.title = element_text(colour = "navy"),
    axis.title.x = element_text(size = 12, face = "bold"),
    axis.title.y = element_text(size = 12, face = "bold", angle = 90),
    axis.text = element_text(colour = "gray30"),
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

axis.title.x / axis.title.y target individual axes. Same for axis.text with axis.text.x / axis.text.y.

Axis Lines and Ticks

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme(
    axis.line = element_line(colour = "black", linewidth = 0.5),
    axis.ticks = element_line(colour = "gray40"),
    axis.ticks.length = unit(0.1, "inches")
  )

Remove ticks entirely:

theme(axis.ticks = element_blank())

Legend Appearance

ggplot(mpg, aes(displ, hwy, colour = class)) +
  geom_point() +
  theme(
    legend.position = "bottom",
    legend.direction = "horizontal",
    legend.background = element_rect(fill = "white", colour = NA),
    legend.key = element_rect(fill = "gray95", colour = NA),
    legend.text = element_text(size = 9),
    legend.title = element_text(size = 10, face = "bold"),
    legend.margin = margin(t = 0.2, unit = "cm")
  )

legend.position accepts "none", "left", "right", "top", "bottom", or a numeric vector for placing inside the plot:

theme(legend.position.inside = c(0.85, 0.15))

Panel Background and Grid

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme(
    panel.background = element_rect(fill = "white", colour = NA),
    panel.border = element_rect(fill = NA, colour = "gray30"),
    panel.grid.major = element_line(colour = "gray80", linewidth = 0.3),
    panel.grid.minor = element_line(colour = "gray90", linewidth = 0.15),
    panel.ontop = FALSE,
    plot.background = element_rect(fill = "#f0f0f0", colour = NA)
  )

panel.ontop = TRUE draws grid lines on top of your data — useful when points would otherwise obscure the grid.

Plot Titles and Caption Alignment

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  labs(
    title = "Engine Size vs Fuel Economy",
    subtitle = "1999-2008 vehicles from fueleconomy.gov",
    caption = "Data: EPA"
  ) +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0),
    plot.subtitle = element_text(size = 11, colour = "gray40", hjust = 0),
    plot.caption = element_text(size = 8, colour = "gray50", hjust = 1),
    plot.title.position = "panel",
    plot.caption.position = "panel",
    plot.margin = margin(t = 0.3, r = 0.3, b = 0.3, l = 0.3, unit = "cm")
  )

plot.title.position controls whether the title aligns relative to the "panel" (data area) or full "plot" area.

Facet Strip Styling

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(class), ncol = 3) +
  theme(
    strip.background = element_rect(fill = "steelblue", colour = NA),
    strip.text = element_text(colour = "white", size = 10, face = "bold"),
    strip.placement = "outside"
  )

strip.placement = "inside" puts facet labels next to the axis rather than at the panel edge.

Stripping a Plot Down

The quickest way to a minimal plot:

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme(
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    axis.ticks = element_blank(),
    axis.line = element_blank(),
    panel.border = element_blank(),
    panel.background = element_blank()
  )

Base Themes Plus Overrides

Built-in complete themes:

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme_minimal(base_size = 11) +
  theme(panel.grid.minor = element_blank())

ggplot(mtcars, aes(disp, mpg)) +
  geom_point() +
  theme_classic()

Available: theme_grey() / theme_gray(), theme_bw(), theme_linedraw(), theme_light(), theme_dark(), theme_minimal(), theme_classic(), theme_void().

Updating the Active Theme Globally

theme_update() changes settings for all plots until you reset:

old <- theme_update(
  panel.background = element_rect(fill = "#fafafa"),
  text = element_text(family = "Helvetica", size = 10)
)

# ... make plots ...

theme_set(old)  # restore previous theme

theme_get() reads the current theme without changing anything.

How Inheritance Works

Theme elements form a hierarchy. Setting a property high up applies it everywhere below:

  • text → all text
  • axis.title → all axis titles → axis.title.xaxis.title.x.bottom

Override at the most specific level needed:

theme(
  text = element_text(family = "Times"),
  axis.title = element_text(colour = "black"),
  axis.title.x = element_text(angle = 45)
)

See Also