ggplot2 Legends in R: Position, Remove, Rename & Customize Completely
ggplot2 legends map visual aesthetics — colour, fill, shape, size — back to the data values they represent. You control them through three levers: theme() for position and appearance, labs() or scale_*() for titles and labels, and guides() for fine-grained guide behaviour.
Introduction
Every ggplot2 plot that maps a variable to a visual aesthetic generates a legend automatically. That automatic legend is functional, but it rarely matches what you need for a report, presentation, or publication. The title might be a column name like cyl, the labels might be cryptic factor levels, and the position might crowd the plot area.
This tutorial gives you full control over ggplot2 legends. You will learn how to move, remove, rename, reorder, and visually customize every part of a legend. Each technique builds on the previous one, so by the end you will be able to produce publication-ready legend layouts from scratch.
All code runs interactively in the browser. We use ggplot2 and the built-in mpg dataset throughout. Let's start by loading our library and creating a base plot that we will reuse across sections.
This base plot maps class to colour, so ggplot2 shows a colour legend on the right. We will modify this legend step by step in every section below.
How does ggplot2 decide which legends to show?
The rule is simple: any variable mapped inside aes() generates a legend. Any aesthetic set to a fixed value outside aes() does not.
Let's see this in action. Our p_base plot maps colour = class inside aes(), so a colour legend appears. Now compare that with setting colour to a fixed value outside aes().
When you set colour = "steelblue" outside aes(), ggplot2 applies that colour to every point. There is no data-to-colour mapping, so no legend is needed.
aes(). If it is not, ggplot2 has no mapping to display.When you map the same variable to multiple aesthetics — say colour = class and shape = class — ggplot2 merges them into a single legend. If you map different variables, you get separate legends for each.
Try it: Create a scatter plot of mpg mapping drv to shape. Verify that a shape legend appears on the right.
Click to reveal solution
Explanation: Mapping drv inside aes(shape = drv) tells ggplot2 to assign a different shape to each drive type, which generates a shape legend automatically.
How do you change the legend position?
The theme(legend.position) argument accepts "top", "bottom", "left", "right", or "inside". The default is "right".
Moving the legend to the top or bottom works well when you have a wide plot and the legend has many items. Let's move our base plot's legend to the bottom.
The legend switches to horizontal layout automatically when placed at the top or bottom. That keeps it compact and out of the way.
For more precise control, you can place the legend inside the plot area. In ggplot2 3.5+, use legend.position = "inside" combined with legend.position.inside to set the exact coordinates (values from 0 to 1, where c(0, 0) is bottom-left and c(1, 1) is top-right).
The white background with a subtle border prevents the legend from blending into the data points. Adjust the c(x, y) coordinates to find the spot with the least overlap.
legend.background = element_rect(fill = "white"), legend text overlaps data points and becomes unreadable.Try it: Place the legend at the bottom-left inside the plot area. Use coordinates c(0.15, 0.25).
Click to reveal solution
Explanation: legend.position.inside = c(0.15, 0.25) places the legend 15% from the left edge and 25% from the bottom.
How do you remove a legend entirely or selectively?
There are three ways to remove legends, and each serves a different purpose. Picking the wrong one is a common source of frustration.
Method 1: Remove all legends at once. Use theme(legend.position = "none"). This is the nuclear option — every legend disappears.
Method 2: Remove one specific legend. Use guides() to target a single aesthetic. This is what you want when your plot has multiple legends and you only need to drop one.
Method 3: Suppress a single layer's legend contribution. Use show.legend = FALSE inside a geom. This is useful when you have overlapping layers and one layer adds unwanted entries.
The show.legend = FALSE on geom_smooth() prevents the smooth lines from adding entries to the colour legend. Without it, you would see both point and line keys for each class.
guides(colour = "none") or guides(size = "none") to target the specific aesthetic.Try it: Create a plot with both colour = class and size = displ mapped. Then remove only the size legend using guides().
Click to reveal solution
Explanation: guides(size = "none") targets the size aesthetic specifically, leaving the colour legend untouched.
How do you rename the legend title and labels?
The legend title defaults to the variable name — class, drv, cyl. That is rarely what you want in a finished plot. There are two things to rename: the title (the heading above the legend) and the labels (the text next to each key).
Rename the title with labs(). This is the quickest approach and handles most cases.
The labs() function accepts the aesthetic name as the argument. Use labs(fill = "...") for fill legends, labs(shape = "...") for shape legends, and so on.
Rename individual labels with scale_colour_discrete(labels = ...). This changes the text next to each key without affecting the underlying data.
Reorder legend items by changing the factor levels of the variable. The legend order follows the factor level order.
Releveling the factor changes both the legend order and the colour assignment. If you want to keep the original colours but just reorder the legend, use limits inside scale_colour_discrete() instead.
Try it: Rename the fill legend title to "Drive Type" using labs() on a bar chart of mpg counted by drv.
Click to reveal solution
Explanation: labs(fill = "Drive Type") sets the title for the fill aesthetic legend. Use the aesthetic name as the argument.
How do you customize legend appearance with theme()?
The theme() function controls every visual property of the legend: fonts, colours, backgrounds, key sizes, spacing, and direction. This is your primary tool for making legends look polished.
Let's start with styling the legend text. You can make the title bold and larger, and the labels italic or a different colour.
The element_text() function accepts face (bold, italic, bold.italic), size, colour, family, and hjust/vjust for alignment. Use it with any legend.* theme element that controls text.
Next, let's customize the legend keys — the small coloured squares or circles next to each label.
The default grey key background (legend.key = element_rect(fill = "grey95")) can make subtle colours hard to see. Setting it to white cleans up the appearance.
You can also change the legend direction from vertical (the default for side placement) to horizontal. This is especially useful when the legend sits at the top or bottom.
When you have many legend items, a single horizontal row can get cramped. Combine legend.direction with guide_legend(nrow = ...) (covered in the next section) to wrap items across multiple rows.
legend.frame, legend.axis.line, and legend.key.spacing are now theme arguments rather than guide parameters. Check the ggplot2 3.5.0 changelog if you are updating old code.Try it: Make the legend keys 1.5 cm wide and remove the grey key background.
Click to reveal solution
Explanation: legend.key.width sets the horizontal size. element_rect(fill = "white", colour = NA) removes both the grey fill and the border.
How do you use guides() and guide_legend() for advanced control?
The guides() function assigns a guide type to each aesthetic, and guide_legend() gives you fine-grained control over layout and key appearance. This is the power tool for complex legend customization.
Control the legend layout with nrow and ncol inside guide_legend(). This is essential when a horizontal legend has too many items for one row.
Two rows keep the legend compact without making the text too small. For 10+ items, use 3 rows.
Override the appearance of legend keys with override.aes. This is the solution when your plot uses tiny or transparent points that are hard to read in the legend.
Without override.aes, the legend keys would be just as tiny and faded as the plot points. The override lets you decouple the legend appearance from the plot aesthetics.
Use guide_colorbar() for continuous scales. When you map a continuous variable to colour, ggplot2 shows a colour bar instead of discrete keys. You can control its dimensions and appearance.
The wide, thin bar works well at the bottom of a plot. Adjust barwidth and barheight to match your figure dimensions.
Try it: Create a colour legend laid out in 3 columns using guide_legend(ncol = 3).
Click to reveal solution
Explanation: guide_legend(ncol = 3) distributes the legend keys across 3 columns. ggplot2 calculates the number of rows automatically.
Common Mistakes and How to Fix Them
Mistake 1: Setting colour outside aes() and expecting a legend
This is the most common legend problem. You set a colour directly, then wonder why no legend appears.
❌ Wrong:
Why it is wrong: ggplot2 only creates legends for aesthetics mapped inside aes(). Setting colour = "red" applies a fixed colour to all points with no data mapping.
✅ Correct:
Mistake 2: Using theme(legend.position = "none") to remove one legend
When your plot has both a colour and a size legend, theme(legend.position = "none") removes both.
❌ Wrong:
Why it is wrong: legend.position = "none" is a blanket removal. It does not discriminate between aesthetics.
✅ Correct:
Mistake 3: Trying to rename labels with labs()
labs() sets the legend title, not the individual labels. Using it and expecting label changes leads to confusion.
❌ Wrong:
Why it is wrong: labs() expects a single string for the title. Pass a named vector to scale_colour_discrete(labels = ...) instead.
✅ Correct:
Mistake 4: Forgetting to relevel the factor when reordering legend items
Changing the order of scale_colour_discrete(limits = ...) reorders the legend but does not reassign colours. The safest approach is to relevel the factor in the data.
❌ Wrong:
Why it is wrong: limits restricts the scale to the values you list. Any value not in limits is treated as NA and removed.
✅ Correct:
Practice Exercises
Exercise 1: Multi-legend scatter with custom layout
Build a scatter plot of mpg with colour = class and size = displ. Apply all of these customizations:
- Move the legend inside the plot at position
c(0.85, 0.7) - Rename the colour title to "Vehicle Class"
- Rename the size title to "Engine Size (L)"
- Make the colour legend horizontal with 2 rows
- Add a white background with a grey border
Click to reveal solution
Explanation: labs() renames both titles. guide_legend(nrow = 2) wraps the 7-class colour legend into 2 rows. The theme() call positions the entire legend box inside the plot with a clean white background.
Exercise 2: Publication-ready bar chart with styled legend
Create a stacked bar chart of mpg counting cars by class, filled by drv. Then apply:
- Remove the fill legend title (set it to blank)
- Relabel fill values: "4" becomes "4WD", "f" becomes "Front", "r" becomes "Rear"
- Make keys square at 1 cm by 1 cm
- Place the legend at the bottom
- Set the legend key background to white
Click to reveal solution
Explanation: labs(fill = NULL) removes the title entirely. scale_fill_discrete(labels = c(...)) uses a named vector to map original values to display labels. The theme() call controls key size and background.
Putting It All Together
Let's build a publication-ready scatter plot from scratch with a fully customized legend. This combines every technique from the tutorial: positioning, renaming, label control, key styling, and layout.
This example hits every lever: labs() for the title, scale_colour_discrete() for labels, factor() for order, guide_legend() for layout and override, and theme() for visual polish. Copy this pattern and swap in your own data and aesthetics.
Summary
| Task | Function | Example |
|---|---|---|
| Move legend | theme(legend.position) |
"bottom", "inside" |
| Place inside plot | theme(legend.position.inside) |
c(0.85, 0.75) |
| Remove all legends | theme(legend.position = "none") |
— |
| Remove one legend | guides(aes = "none") |
guides(size = "none") |
| Suppress a layer | show.legend = FALSE |
geom_smooth(show.legend = FALSE) |
| Rename title | labs(aes = "...") |
labs(colour = "Class") |
| Rename labels | scale_*_discrete(labels) |
labels = c("a" = "Alpha") |
| Reorder items | factor(levels = ...) |
Relevel the mapped variable |
| Style text | theme(legend.title, legend.text) |
element_text(face = "bold") |
| Style keys | theme(legend.key, legend.key.size) |
element_rect(fill = "white") |
| Layout rows/cols | guide_legend(nrow, ncol) |
guide_legend(nrow = 2) |
| Override key look | guide_legend(override.aes) |
list(size = 4, alpha = 1) |
| Continuous bar | guide_colorbar() |
barwidth, barheight |
FAQ
How do I merge two legends into one?
Map the same variable to both aesthetics and give them the same title with labs(). For example, use aes(colour = class, shape = class) plus labs(colour = "Class", shape = "Class"). This merges the colour and shape legends into one combined legend.
What is the difference between guide_legend() and guide_colorbar()?
guide_legend() creates a legend with discrete keys — one per level. guide_colorbar() creates a continuous colour gradient bar. ggplot2 picks the right one automatically based on the scale type, but you can override it inside guides().
How do I change just the legend key shape?
Use the key_glyph argument inside the geom. For example, geom_line(aes(colour = group), key_glyph = draw_key_rect) uses rectangles instead of lines as legend keys. Other options include draw_key_point, draw_key_dotplot, and draw_key_path.
Why does my legend show the letter "a" inside the keys?
This happens when geom_text() or geom_label() is in your plot. The text geom adds its label character to the legend key. Fix it by setting show.legend = FALSE on the text layer, or use guides(colour = guide_legend(override.aes = list(label = ""))) to blank out the text in the legend only.
References
- ggplot2 documentation — guides() reference. Link
- ggplot2 documentation — guide_legend() reference. Link
- ggplot2 documentation — theme() reference. Link
- Wickham, H. — ggplot2: Elegant Graphics for Data Analysis, 3rd Edition. Springer (2024). Chapter 11: Scales and Guides. Link
- Tidyverse Blog — ggplot2 3.5.0: Legends (February 2024). Link
- R-Charts — Legends in ggplot2. Link
What's Next?
- ggplot2 Themes — Master
theme_minimal(),theme_classic(), and build your own reusable custom theme. Read more - ggplot2 Colour Scales — Control colours with
scale_colour_manual(), viridis, and ColorBrewer palettes for accessible, beautiful plots.