Styling with theme(), element_text(), and guides()

Recall, that ggplot2 graphics are built in layers. The first layer is the data and the mapping of data to aesthetics. The second layer is the scales and guides that control how the data-mapped aesthetics are displayed. The third layer is the theme, which controls the non-data ink on the canvas.

Layers in ggplot2

Data Mapping (aes) ───► What data is plotted and mapped

Scales / Guides ───► How data-mapped aesthetics translate and display

Themes / Elements ───► The canvas styling (non-data ink)

This document focuses on the third layer, the theme, and how to control it with theme(), element_text(), and guides().

1. theme()

theme() controls all non-data visual elements of the plot: it never touches data points, lines, bars, or coordinates, and instead styles the frame, grid lines, panel backgrounds, margins, and legend positioning.

Rule of thumb: if a visual property is derived directly from data values (e.g., coloring points by vehicle class), it belongs in aes() or scale_*(). If it’s a fixed stylistic choice across the whole plot (e.g., background color, legend position), it belongs in theme().

2. element_text()

Inside theme(), text elements (titles, subtitles, axis labels, tick marks, legend titles) can’t take raw strings or plain numbers — ggplot2 uses constructor functions to define their appearance. element_text() handles typography.

Related constructors:

  • element_text(): typography and font styling
  • element_rect(): borders, fills, and backgrounds
  • element_line(): grid lines and axis tick marks
  • element_blank(): removes the element completely

Key arguments to element_text():

Argument Description Values / Examples
face Font style/weight "bold", "italic", "plain"
size Text size in points Numeric (e.g., 12, 14)
color Font color Hex codes ("#2b2b2b") or names ("gray30")
angle Text rotation Degrees (0 to 360, e.g., 45 for crowded x-axes)
hjust / vjust Horizontal / vertical alignment 0 (left/bottom), 0.5 (center), 1 (right/top)
family Font family/typeface "sans", "serif", "mono", or custom fonts
theme(
  plot.title   = element_text(face = "bold", size = 16, hjust = 0),
  axis.text.x  = element_text(angle = 45, hjust = 1, size = 10)
)

3. guides()

While theme() manages non-data ink, guides() controls how legends and colorbars behave, wrap, and render.

The distinction:

  • theme(legend.position = "bottom") controls where the legend container sits on the canvas.
  • guides(color = guide_legend(nrow = 2)) controls how the keys inside that legend are laid out.
  • guides(fill = "none") removes a specific legend entirely without dropping the aesthetic mapping.

Common use cases:

  • Wrapping long discrete legends into multiple rows/columns with guide_legend(nrow = 2).
  • Fixing point visibility in the legend (override.aes): if points are drawn with low opacity (alpha = 0.3) or small size (size = 1), they can be hard to see in the legend key. override.aes adjusts the legend keys without changing the plot data.
  • Shaping continuous colorbars with guide_colorbar(barwidth = unit(12, "lines")).

Reference:

Function Scope Question it answers Example
theme() Canvas / frame Where is this non-data element placed or styled? Moving the legend box, dropping grid lines
element_text() Typography How should this specific label look? Rotating tick labels, bolding titles
guides() Legend keys How should the legend contents be arranged? Stacking legend keys into 2 rows, boosting legend alpha