Writing Vignettes in R

· 7 min read · Updated March 30, 2026 · intermediate
r packages documentation vignettes

A vignette is a long-form article bundled inside an R package. It is not a reference page for a single function — it is a narrative document that shows how your package’s pieces work together in a real workflow. Users find vignettes with vignette("topic") or browseVignettes(), and on package websites, pkgdown renders them automatically as HTML articles.

This tutorial covers the full lifecycle: creating a vignette with usethis, writing the .Rmd structure, wiring it into DESCRIPTION, building and testing, and understanding what belongs in a vignette versus a roxygen2 reference page.

Prerequisites

Before writing a vignette, you need a working package skeleton. At minimum, you need a DESCRIPTION file and an R/ directory with at least one .R file.

# Create a minimal package skeleton with usethis
usethis::create_package("~/projects/mypackage")
# ✔ Creating '/home/user/projects/mypackage/'
# ✔ Setting active project to '/home/user/projects/mypackage'

If your package already exists, you can add the vignette infrastructure directly:

usethis::use_vignette("intro")
# ✔ Adding Vignettes field to DESCRIPTION
# ✔ Creating vignettes/intro.Rmd
# ✔ Adding knitr to Suggests

use_vignette() creates the vignettes/ directory if it does not exist, scaffolds an .Rmd file with correct YAML frontmatter, and updates DESCRIPTION automatically.

Anatomy of an R Markdown Vignette

Open the generated vignettes/intro.Rmd. The file has two parts: a YAML header and the document body.

The YAML Frontmatter

---
title: "intro"
author: Your Name
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: |
  %\VignetteIndexEntry{intro}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

The vignette: block contains three special declarations that R CMD build reads:

  • %VignetteIndexEntry{} — the title shown in vignette listings
  • %VignetteEngine{} — tells R which program processes this file (knitr::rmarkdown is the standard choice)
  • %VignetteEncoding{} — declares the character encoding

Without all three, R CMD build may skip the vignette silently or fail to display it correctly.

The output: rmarkdown::html_vignette line produces clean HTML output sized for reading. It is the standard output format for vignettes on CRAN and with pkgdown.

The Body

A practical vignette covers these sections in order:

  1. Introduction — one or two paragraphs explaining what the package does and who it is for.
  2. Installation — a short code block showing how to install the package.
  3. Core workflow — a realistic use case, chunk by chunk, with input and output visible.
  4. Session information — a call to sessionInfo() at the end.

Here is a minimal example using a fictional foobar package:

## Introduction

The **foobar** package provides functions to transform and summarise
numeric vectors. It is designed for exploratory data analysis workflows
where you need quick descriptive statistics without verbose syntax.

## Installation

```r
# Install from CRAN
install.packages("foobar")

# Or install the development version from GitHub
remotes::install_github("yourname/foobar")

Basic usage

Load the package and run a simple transformation:

library(foobar)

x <- c(3.1, 4.5, 2.8, 9.0, 6.3)
summarise_vector(x)
# [1] "mean: 5.14, sd: 2.49"

The summarise_vector() function computes the mean and standard deviation of a numeric vector and returns a formatted string. This is useful when you want a quick textual summary during interactive analysis.

Advanced usage

You can pass additional arguments to control the number of decimal places:

summarise_vector(x, dp = 1)
# [1] "mean: 5.1, sd: 2.5"

Session information

sessionInfo()
# R version 4.4.0 (2024-04-24)
# Platform: x86_64-pc-linux-gnu
# ...
# attached base packages:
# [1] stats     graphics  grDevices utils     datasets

The sessionInfo() output at the end of a vignette helps readers reproduce your exact environment. This is especially important when your vignette uses packages that have changed behaviour across versions.

Wiring Vignettes into DESCRIPTION

The DESCRIPTION file tells R which vignettes exist and how to find them. usethis::use_vignette() updates this automatically, but it helps to understand the structure.

Vignettes:
  intro: Introduction to foobar (\code{intro.Rmd})

Each entry maps a topic name (used in vignette("intro")) to the source file path. If you add a second vignette at vignettes/advanced.Rmd, add another entry:

Vignettes:
  intro: Introduction to foobar (\code{intro.Rmd})
  advanced: Advanced topics (\code{advanced.Rmd})

The topic name defaults to the filename without extension if you omit the explicit label, but it is safer to list them explicitly so renaming files does not break references.

Roxygen2 vs Vignettes

A common source of confusion is deciding whether something belongs in a roxygen2 comment or a vignette. They serve different purposes and are not interchangeable.

roxygen2 generates the reference manual — the .Rd files that power ?function_name. It documents what individual parameters mean, what a function returns, and provides runnable examples. roxygen2 lives inside .R source files as #' comments.

Vignettes are narrative articles. They show end-to-end workflows and explain the “why” behind your design decisions. They cannot replace reference docs, and reference docs cannot replace vignettes.

Use this rule of thumb: document parameters in roxygen2, document workflows in vignettes. If you find yourself writing a long list of #' @param entries, that belongs in an .R file. If you want to show how three functions combine to solve a real problem, that belongs in a vignette.

Note that vignettes are not generated from roxygen2 comments. They are standalone .Rmd files that live in vignettes/ and are rendered separately during the package build.

Building and Testing

During development, use devtools to build and check your vignettes:

# Build the package tarball, rendering vignettes
devtools::build()

# Run R CMD check — this builds and runs vignettes in an isolated environment
devtools::check()

If a vignette throws an error during check, R CMD check will fail. Vignette code must be fully runnable.

To read a vignette from a locally installed package:

vignette("intro")           # load and display the intro vignette
browseVignettes()           # list all vignettes in loaded packages

If vignette() reports “there is no vignette with name …”, the most likely cause is installing from a pre-built binary that stripped vignettes. Install from source instead:

install.packages("mypackage", type = "source")
# Or use devtools, which builds vignettes by default
devtools::install()

Dependencies and Suggests

If your vignette loads a package with library(), that package needs to be listed in Suggests: in DESCRIPTION — not just Imports:. During R CMD check, only packages in Imports: and Depends: are guaranteed to be available. Packages listed only in Suggests: may not be installed in the check environment.

Imports:
  foobar
Suggests:
  ggplot2,
  testthat

The usethis::use_vignette() helper adds knitr and rmarkdown to Suggests: automatically.

Common Problems

vignette("name") returns nothing. Vignettes were likely stripped during installation. Use devtools::install() or install.packages(..., type = "source").

R CMD build skips the vignette silently. Check that the DESCRIPTION Vignettes: field exists and that the %VignetteEngine{} declaration is present in the .Rmd frontmatter. Without the engine declaration, R CMD build does not know how to process the file.

“duplicate label” error from knitr. Every code chunk in an .Rmd vignette must have a unique label. knitr uses labels to track chunk dependencies. If two chunks share a label (for example, both named “setup”), compilation fails. Rename one of the chunks to resolve this.

Suggested package not found during check. Any package your vignette loads with library() must be in Suggests:. Move truly core packages to Imports: if they are central to the package’s purpose.

What Goes in a Vignette

Focus on a single use case. Show a realistic input, run it, and display the output. Explain why each step matters — not just what the code does.

Keep the code self-contained. Use built-in datasets or create small synthetic data rather than relying on external files. Vignettes must run identically on any machine.

Include sessionInfo() at the end. It costs one line and makes the vignette significantly more reproducible.

What Does Not Belong in a Vignette

Do not write a vignette that lists every function in your package in alphabetical order. That is a reference manual, not a vignette, and roxygen2 already generates one for you.

Do not copy your DESCRIPTION title and repeat it verbatim. The vignette is where you expand on what that title means in practice.

Do not include large test suites or pages of edge-case code. Save those for test files and roxygen2 @examples blocks.

See Also

Conclusion

Writing a vignette is the clearest way to show users what your package actually does. A good vignette takes a single workflow and walks through it completely: install, load, run, explain, show output. It lives in vignettes/ as an .Rmd file, connects to DESCRIPTION via the Vignettes: field, and builds with devtools::build().

The payoff is real. Users who read a well-written vignette understand your package in a way that no amount of reference documentation can provide. Set aside the reference manual and write the story instead.