# ggtext brings a small subset of Markdown to ggplot2 labels

> ggtext is an R package that renders Markdown and HTML inside ggplot2 theme elements and in geoms, using two new theme elements, element_markdown and element_textbox, with the actual drawing done by the gridtext package. The feature set is deliberately narrow, bold and italics, font, colour and size changes, sub and superscript and rudimentary images, and the package sits on CRAN with a single 0.1.0 release from 2020 while the repository was last pushed on 2026-08-26.

**wilkelab/ggtext** — Improved text rendering support for ggplot2

- Repository: https://github.com/wilkelab/ggtext
- Website: https://wilkelab.org/ggtext/
- Stars: 667 · Forks: 38
- Language: R
- License: GPL-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/wilkelab-ggtext

## Two theme elements replace element_text in a theme block

ggtext exists because ggplot2's own text elements render plain strings, and a plot title with a bold phrase and an italic phrase in it is not possible with the default theme elements. The package defines two new theme elements, element_markdown and element_textbox, and both behave similarly to element_text while rendering the provided text as Markdown or HTML.

The split between them is about layout rather than about formatting. element_markdown is the direct replacement for element_text and does not wrap text. To start a new line in it, you use the br tag or add two spaces before the end of the line, which is the same convention Markdown itself uses for a hard line break.

element_textbox is the one for larger amounts of text that need word wrapping. It is not a drop-in replacement for everything, though. The README states that it cannot be used for axis tick labels, and it cannot draw text at arbitrary angles, only at the fixed orientations of 0, 90, 180 and 270 degrees.

That last restriction is the practical difference between the two elements. A plot subtitle or a facet strip can be a wrapped text box, but a diagonal axis title cannot, and a category axis label cannot be a text box at all.

In practice the README recommends element_textbox_simple over element_textbox, because it sets useful defaults for the many parameters that are not usually defined in ggplot2 themes.

## The feature set is the gridtext feature set, and it is small

The most important thing to understand about ggtext is that it is a thin layer. The README says so directly: under the hood, the package uses the gridtext package for the actual rendering, and consequently it is limited to the feature set provided by gridtext.

That set is small, and the README is unusually clear about the boundary. The gridtext package implements only an extremely limited subset of Markdown, HTML and CSS. It can make text bold or italics, change the font, colour or size of a piece of text, place text as sub or superscript, and has extremely rudimentary image support. No other features are currently supported.

The general rule the README gives is a good one to keep: any Markdown, HTML or CSS feature that is not shown in the ggtext or gridtext documentation likely does not exist. That is a stricter statement than a feature list, and it is the right mental model, because a plot label that silently drops a tag is worse than one that visibly fails.

It helps to know what this rules out in practice. There is no link rendering, no list or block structure, no table, and no general CSS layout. What you have is inline emphasis, inline styling of colour, size and font, vertical alignment through sub and superscript, and images.

The engine choice also explains the two-step API. Because the rendering is a separate engine, the formatting vocabulary is HTML and CSS rather than Markdown alone, and you get both in the same string, which is why a colour is applied with an inline style on a span or an i element.

## The theme example shows the mixing of emphasis and colour

The README's worked example is a plot with coloured and italic category labels, built by wrapping the label text in an i element with an inline style, and by marking the theme elements as markdown:

```r
library(ggtext)
library(glue)

data %>% mutate(
  color = c("#009E73", "#D55E00", "#0072B2", "#000000"),
  name = glue("<i style='color:{color}'>{bactname}</i> ({OTUname})"),
  name = fct_reorder(name, value)
)  %>%
  ggplot(aes(value, name, fill = color)) + 
  geom_col(alpha = 0.5) + 
  scale_fill_identity() +
  theme(
    axis.text.y = element_markdown(),
    plot.caption = element_markdown(lineheight = 1.2)
  )
```

Three techniques are on display. The label string is assembled with glue so the colour is interpolated into the style attribute, the emphasis is expressed as an i element rather than a Markdown asterisk, and the theme elements that need it are switched to element_markdown.

The caption shows the Markdown side, with a bold phrase and a br tag for the line break, and the lineheight argument controls the spacing of the wrapped lines.

Note the asymmetry in how emphasis is written. Italics appear as an HTML element and colour as an inline CSS style, while bold in the caption uses Markdown syntax. Both work, and mixing them in one string is normal, but it means the label text is a small template rather than prose, and someone generating those labels in code has to escape the braces and quotes properly.

## Installing from CRAN or tracking the development version

The stable release is on CRAN and installs with the standard call:

```r
install.packages("ggtext")
```

The development version comes from GitHub through the remotes package:

```r
remotes::install_github("wilkelab/ggtext")
```

That second form matters for anyone who wants the newest behaviour, and for anyone who wants to file an issue against a fix that has not reached CRAN yet.

The package is a normal R package in structure. The repository contains a DESCRIPTION file, a NAMESPACE, R/ with the source, man/ with the documentation, tests/, vignettes/ and a pkgdown configuration in _pkgdown.yml, and a .Rbuildignore and a .codecov.yml for the build and coverage configuration. The README is generated from README.Rmd, which is the usual arrangement for a package where the examples are executed when the documentation is built, and it is why the examples in the README carry output comments.

The continuous integration badge points at an R-CMD-check workflow, which is the standard check that runs the package tests across R versions and platforms, and there is a separate codecov status badge for the coverage report.

The lifecycle badge in the README is worth reading, because R packages are expected to declare their maturity. ggtext is marked as maturing, which is the stage for a package that is used in production but is not yet expected to be a stable interface that others build on heavily.

## Images work as axis labels, which is the surprising capability

The one thing in the feature list that goes beyond typography is the img tag, and the README calls the support very basic. The example uses it to put an image in an axis label, with the text following it after a br tag and a Markdown italic name:

```r
labels <- c(
  setosa = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Kosaciec_szczecinkowaty_Iris_setosa.jpg/250px-Kosaciec_szczecinkowaty_Iris_setosa.jpg'
    width='100' /><br>*I. setosa*",
  virginica = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/Iris_virginica.jpg/330px-Iris_virginica.jpg'
  width='100' /><br>*I. virginica*",
  versicolor = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Iris_versicolor_3.jpg/330px-Iris_versicolor_3.jpg'
  width='100' /><br>*I. versicolor*"
)

ggplot(iris, aes(Species, Sepal.Width)) +
  geom_boxplot() +
  scale_x_discrete(
    name = NULL,
    labels = labels
  ) +
  theme(
    axis.text.x = element_markdown(color = "black", size = 11)
  )
```

The images are remote URLs, and the width attribute is given in pixels, so the images are fetched when the plot is rendered. In a report pipeline that renders plots on a machine without network access, that is a failure mode worth knowing about, and the alternative is a local file path if the engine supports it.

The pattern itself is the interesting part. A category axis label becomes an image with a text caption, which is a common request in taxonomy plots where the reader wants to see the organism, and the workaround in plain ggplot2 would have been an annotation, a custom grob, or a separate legend.

The README is clear that this support is rudimentary, so treat the img tag as a small convenience rather than a layout system.

## Text boxes add fill, border and padding to wrapped labels

element_textbox and element_textbox_simple are the elements for larger text, and their parameter list is where the difference from plain text shows. A title in the README's example is a filled box with a border, padding and a margin:

```r
  theme(
    plot.title.position = "plot",
    plot.title = element_textbox_simple(
      size = 13,
      lineheight = 1,
      padding = margin(5.5, 5.5, 5.5, 5.5),
      margin = margin(0, 0, 5.5, 0),
      fill = "cornsilk"
    ),
    axis.title.x = element_textbox_simple(
      width = NULL,
      padding = margin(4, 4, 4, 4),
      margin = margin(4, 0, 0, 0),
      linetype = 1,
      r = grid::unit(8, "pt"),
      fill = "azure1"
    ),
    axis.title.y = element_textbox_simple(
      hjust = 0,
      orientation = "left-rotated",
      minwidth = unit(1, "in"),
      maxwidth = unit(2, "in"),
      padding = margin(4, 4, 2, 4),
      margin = margin(0, 0, 2, 0),
      fill = "lightsteelblue1"
    )
  )
```

The parameters are the standard grid graphics ones. fill is the background colour, linetype and r draw the border and its radius, padding is the space inside the box, margin the space outside it, lineheight the leading between wrapped lines, and width, minwidth and maxwidth control the box width, with NULL meaning the width follows the text.

The orientation argument on the vertical axis title shows how the fixed-angle limitation is worked with in practice. The element cannot rotate to an arbitrary angle, so the vertical title is left-rotated by name, one of the permitted orientations, with a width range in inches to keep it in a sensible shape.

The facet strip example at the end of the README extends the same idea to strip text, replacing the default strip background with element_blank and giving the strip text a filled box with its own border and padding, which is a common treatment for facetted plots with long category names.

## Maintenance, licensing and where ggtext stops

The maintenance picture is unusual and worth describing accurately. The repository is not archived and the last push was on 2026-08-26, so the code is still being touched, but the release history has a single entry: version 0.1.0, published on 2020-06-04. The package is on CRAN and the lifecycle badge marks it as maturing.

In practice that means the CRAN version most people install is the 0.1.0 from 2020, and anything newer has to come from the GitHub install path. The README's own example output carries a warning that the tidyverse packages were built under a newer R version, which is a small sign of the age of the pinned versions in the example.

The licence is GPL-2.0, which is the common licence for R packages of this kind, and it comes with the usual obligations for a copyleft work. This is a description of the terms rather than legal advice.

The boundary of what ggtext can do is the real limit to plan around. It is a formatting layer for labels, not a layout engine, and the underlying gridtext supports only emphasis, font, colour, size, vertical alignment and rudimentary images. If a chart needs a text block with a link, a footnote with mixed fonts, or a rotated paragraph, you are back to grid graphics and custom grobs, which is where the effort of this package stops.

The alternative approaches are worth naming. Plain geom_text with a colour argument covers the case of a single styled label. The patchwork and annotation packages cover a different problem, adding elements to a composed plot. And for the specific case of a chart that mixes emphasis in a title, editing the theme element and accepting the limits, as the README suggests, is often the least work of all.

## Conclusion

Adopt ggtext when your ggplot2 charts need mixed emphasis, coloured axis labels or a wrapped text box, and you are content with the narrow formatting subset that gridtext supports. Do not adopt it expecting a full Markdown or HTML renderer, since links, lists, tables and most CSS simply are not there, and do not use it for text that must be laid out at an arbitrary angle. Verify first by rendering one label containing the exact markup you plan to use, because the README states that any feature not shown in the documentation probably does not exist.

## FAQ

### What does ggtext add to ggplot2?

It renders a limited subset of Markdown and HTML in theme elements such as plot titles, subtitles, captions, axis labels and legends, and in geoms similar to geom_text. The rendering itself is done by the gridtext package, so ggtext is limited to the feature set that gridtext provides.

### What is the difference between element_markdown and element_textbox?

element_markdown is a direct replacement for element_text and does not wrap text, with line breaks coming from a br tag or two trailing spaces. element_textbox is for larger text that needs word wrapping, but it cannot be used for axis tick labels and only supports the fixed orientations of 0, 90, 180 and 270 degrees.

### How do I install ggtext?

Install the CRAN release with install.packages("ggtext"), or the development version with remotes::install_github("wilkelab/ggtext"). The CRAN release is version 0.1.0 from 2020-06-04, so the GitHub route is the one to use if you need newer behaviour.

## Sources

- [License: GPL-2.0](https://github.com/wilkelab/ggtext/blob/master/LICENSE)
- [Project website](https://wilkelab.org/ggtext/)
- [README](https://github.com/wilkelab/ggtext/blob/master/README.md)
- [Releases](https://github.com/wilkelab/ggtext/releases)
- [wilkelab/ggtext on GitHub](https://github.com/wilkelab/ggtext)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wilkelab-ggtext
