Written and maintained by CASRAI Editorial Board
Published · Last updated
On this page
To rotate x axis labels in ggplot2, add theme(axis.text.x = element_text(angle = 90)) to the plot, or use guides(x = guide_axis(angle = 90)). The second route is the one most tutorials skip: the ggplot2 reference says guide_axis() “also uses some heuristics” to pick the horizontal and vertical justification for you, which is the step that usually goes wrong when you rotate by hand. This guide shows both routes, then two fixes that avoid rotation altogether (n.dodge and check.overlap).
What it is used for in research
Categorical axes in research figures often carry long labels: treatment arms, site names, gene or sample identifiers, survey items. Horizontal labels collide, and journals and data-management plans expect figures to be legible without manual editing in a graphics program. Doing the rotation in code keeps the figure reproducible from the script, which fits the practice described in ggplots in R: build, style and save plots with ggplot2.
Install or access (official source only)
ggplot2 is an R package distributed through CRAN, the official R package repository. Install it once and load it in each session:
install.packages("ggplot2")
library(ggplot2)
The examples below use the mpg data set that ships with ggplot2, whose manufacturer column holds 15 text categories. The reference pages cited here are for ggplot2 4.0.3, checked on 2026-10-29.
Step-by-step: rotate, stagger or thin x axis labels
Start from a plain bar chart and add one fix at a time.
p <- ggplot(mpg, aes(manufacturer)) + geom_bar()
1. Rotate with theme() and element_text()
The label text on the x axis is controlled by the theme element axis.text.x, which the theme() reference lists among the “tick labels along axes” elements and which takes an element_text(). Set its angle argument:
p + theme(axis.text.x = element_text(angle = 90))
The element reference gives the allowed range for angle as 0 to 360 and the range for hjust and vjust as 0 to 1. A rotated label is anchored by those two settings, so add them and adjust until each label sits under its tick:
p + theme(axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1))
The hjust and vjust values here are starting points to adjust for your own angle and font size, not fixed rules.
2. Rotate with guide_axis(angle)
The same result can be requested on the axis guide:
p + guides(x = guide_axis(angle = 90))
This form appears in the guide_axis() reference examples. Its documentation states that, compared with setting the angle in theme() or element_text(), it “also uses some heuristics” to automatically pick the hjust and vjust you probably want. If you only need a rotation and have no other styling reason to touch the theme, this is the shorter route.
3. Stagger labels with n.dodge
Instead of rotating, n.dodge spreads the labels over several rows. The reference defines it as the number of rows (for vertical axes) or columns (for horizontal axes) used to render the labels:
p + scale_x_discrete(guide = guide_axis(n.dodge = 2))
The reference documents the same argument on a continuous scale (scale_x_continuous(guide = guide_axis(n.dodge = 2))), so it works for numeric axes too. Staggered labels stay horizontal, which is easier to read than angled text when you have only a few crowded labels.
4. Thin labels with check.overlap
Setting check.overlap = TRUE makes ggplot2 “silently remove overlapping labels, (recursively) prioritizing the first, last, and middle labels”:
p + guides(x = guide_axis(check.overlap = TRUE))
Use this only when dropping some labels is acceptable for the figure. In a categorical chart where every bar must be identifiable it is the wrong choice, because the missing labels are not reported.
Output and how to read it
Each variant returns the same bars; only the label placement changes. Check three things before saving the figure: every category is still labelled (rules out check.overlap for categorical data), no label runs off the plot edge, and each rotated label ends at its own tick rather than between two ticks. If the labels look shifted, the cause is the justification, not the angle, which leads to the next section. For titles on the rotated axis see ggplot titles with labs(), and for the wider theme system see ggplot themes.
Troubleshooting
Why are my rotated labels not under the ticks?
Rotation with element_text(angle = ...) changes the text direction but leaves the justification at its defaults, so labels can look offset. Set hjust and vjust in the documented 0 to 1 range and adjust, or switch to guide_axis(angle = ...), which picks them heuristically.
Which theme element do I change?
The theme reference lists axis.text (all axes), axis.text.x (the x axis) and axis.text.x.top (an x axis drawn at the top). Setting axis.text.x rotates only the x axis; setting axis.text would affect the y axis as well.
Does the angle accept values outside 0 to 360?
The element reference documents the range as 0 to 360, so stay inside it.
My axis is on a log scale or needs breaks changed
Label rotation is independent of the scale. See ggplot log scale for transformed axes and axis in R for titles, limits, breaks and labels.
Free alternatives: rotate, stagger or thin
All four approaches below are part of ggplot2 itself, which is free open-source software distributed through CRAN.
| Approach | Code | What the documentation says it does |
|---|---|---|
| Rotate via theme | theme(axis.text.x = element_text(angle = 90)) |
angle accepts 0 to 360; hjust and vjust accept 0 to 1 |
| Rotate via guide | guides(x = guide_axis(angle = 90)) |
Uses heuristics to pick hjust and vjust automatically |
| Stagger | guide_axis(n.dodge = 2) |
Sets the number of rows or columns used to render the labels |
| Thin | guide_axis(check.overlap = TRUE) |
Silently removes overlapping labels, prioritizing the first, last and middle labels |
FAQ
How do I rotate x axis labels 45 degrees in ggplot2?
Use theme(axis.text.x = element_text(angle = 45, hjust = 1)) and tune hjust and vjust until the labels sit under the ticks, or use guides(x = guide_axis(angle = 45)), since angle accepts a number of degrees.
What is the difference between element_text(angle) and guide_axis(angle)?
Both rotate the text. According to the ggplot2 reference, guide_axis(angle) additionally uses heuristics to choose hjust and vjust, whereas element_text() leaves those to you.
Can I avoid rotating the labels?
Yes. n.dodge staggers them over several rows or columns and check.overlap drops overlapping ones.
Sources (official docs)
- ggplot2 reference: guide_axis(), checked 2026-10-29 (ggplot2 4.0.3)
- ggplot2 reference: element_text() and other theme elements, checked 2026-10-29
- ggplot2 reference: theme(), checked 2026-10-29







