CLI for R Packages
When to Use What
task: Display error with context and formatting use: cli_abort() with inline markup and bullet lists
task: Show warning with formatting use: cli_warn() with inline markup
task: Display informative message use: cli_inform() with inline markup
task: Show progress for counted operations use: cli_progress_bar() with total count
task: Show simple progress steps use: cli_progress_step() with status messages
task: Format code or function names use: {.code ...} or {.fn package::function}
task: Format file paths use: {.file path/to/file}
task: Format package names use: {.pkg packagename}
task: Format variable names use: {.var variable_name}
task: Format values use: {.val value}
task: Handle singular/plural text use: {?s} or {?y/ies} with pluralization
task: Create headers use: cli_h1() , cli_h2() , cli_h3()
task: Create alerts use: cli_alert_success() , cli_alert_danger() , cli_alert_warning() , cli_alert_info()
task: Create lists use: cli_ul() , cli_ol() , cli_dl() with cli_li()
Inline Markup Essentials
Use inline markup with {.class content} syntax to format text:
Basic formatting
cli_text("Function {.fn mean} calculates averages") cli_text("Install package {.pkg dplyr}") cli_text("See file {.file ~/.Rprofile}") cli_text("{.var x} must be numeric, not {.obj_type_of {x}}") cli_text("Got value {.val {x}}"))
Code formatting
cli_text("Use {.code sum(x, na.rm = TRUE)}")
Paths and arguments
cli_text("Reading from {.path /data/file.csv}") cli_text("Set {.arg na.rm} to TRUE")
Types and classes
cli_text("Object is {.cls data.frame}")
Emphasis
cli_text("This is {.emph important}") cli_text("This is {.strong critical}")
Fields
cli_text("The {.field name} field is required")
Vector Collapsing
Vectors are automatically collapsed with commas and "and":
pkgs <- c("dplyr", "tidyr", "ggplot2") cli_text("Installing packages: {.pkg {pkgs}}") #> Installing packages: dplyr, tidyr, and ggplot2
files <- c("data.csv", "script.R") cli_text("Found {length(files)} file{?s}: {.file {files}}") #> Found 2 files: data.csv and script.R
Escaping Braces
Use double braces {{ and }} to escape literal braces:
cli_text("Use {{variable}} syntax in glue") #> Use {variable} syntax in glue
For complete markup reference: See references/inline-markup.md for all 50+ inline classes, edge cases, nesting rules, and advanced patterns.
Pluralization Basics
Use {?} for pluralization with three patterns:
Single Alternative
nfile <- 1 cli_text("Found {nfile} file{?s}") #> Found 1 file
nfile <- 3 cli_text("Found {nfile} file{?s}") #> Found 3 files
Two Alternatives
ndir <- 1 cli_text("Found {ndir} director{?y/ies}") #> Found 1 directory
ndir <- 5 cli_text("Found {ndir} director{?y/ies}") #> Found 5 directories
Three Alternatives (zero/one/many)
nfile <- 0 cli_text("Found {nfile} file{?s}: {?no/the/the} file{?s}") #> Found 0 files: no files
nfile <- 1 cli_text("Found {nfile} file{?s}: {?no/the/the} file{?s}") #> Found 1 file: the file
nfile <- 3 cli_text("Found {nfile} file{?s}: {?no/the/the} file{?s}") #> Found 3 files: the files
Helpers: qty() and no()
Use no() to display "no" instead of zero:
nfile <- 0 cli_text("Found {no(nfile)} file{?s}") #> Found no files
Use qty() to set quantity explicitly:
nupd <- 3 ntotal <- 10 cli_text("{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates") #> 3/10 files need updates
For advanced pluralization: See references/inline-markup.md for edge cases and complex patterns.
CLI Conditions: Core Patterns
Use cli conditions instead of base R for better formatting:
cli_abort() - Formatted Errors
Before (base R)
stop("File not found: ", path)
After (cli)
cli_abort("File {.file {path}} not found")
With bullets for context
check_file <- function(path) { if (!file.exists(path)) { cli_abort(c( "File not found", "x" = "Cannot read {.file {path}}", "i" = "Check that the file exists" )) } }
cli_warn() - Formatted Warnings
Before (base R)
warning("Column ", col, " has missing values")
After (cli)
cli_warn("Column {.field {col}} has missing values")
With context
cli_warn(c( "Data quality issues detected", "!" = "Column {.field {col}} has {n_missing} missing value{?s}", "i" = "Consider using {.fn tidyr::drop_na}" ))
cli_inform() - Formatted Messages
Before (base R)
message("Processing ", n, " files")
After (cli)
cli_inform("Processing {n} file{?s}")
With structure
cli_inform(c( "v" = "Successfully loaded {.pkg dplyr}", "i" = "Version {packageVersion('dplyr')}" ))
Bullet Types
-
"x"
-
Error/problem (red X)
-
"!"
-
Warning (yellow !)
-
"i"
-
Information (blue i)
-
"v"
-
Success (green checkmark)
-
"*"
-
Bullet point
-
">"
-
Arrow/pointer
For advanced error design: See references/conditions.md for error design principles, rlang integration, testing strategies, and real-world patterns.
Basic Progress Indicators
Simple Progress Steps
process_data <- function() { cli_progress_step("Loading data") data <- load_data()
cli_progress_step("Cleaning data") clean <- clean_data(data)
cli_progress_step("Analyzing data") analyze(clean) }
Basic Progress Bar
process_files <- function(files) { cli_progress_bar("Processing files", total = length(files))
for (file in files) { process_file(file) cli_progress_update() } }
Auto-Cleanup
Progress bars auto-close when the function exits:
process <- function() { cli_progress_bar("Working", total = 100) for (i in 1:100) { Sys.sleep(0.01) cli_progress_update() }
No need to call cli_progress_done() - auto-closes
}
For advanced progress: See references/progress.md for nested progress, custom formats, parallel processing, all progress variables, and Shiny integration.
Semantic CLI Elements
Headers
cli_h1("Main Section") cli_h2("Subsection") cli_h3("Detail")
Alerts
cli_alert_success("Operation completed successfully") cli_alert_danger("Critical error occurred") cli_alert_warning("Potential issue detected") cli_alert_info("Additional information available")
Text and Code
Regular text with markup
cli_text("This is formatted text with {.emph emphasis}")
Code blocks
cli_code(c( "library(dplyr)", "mtcars %>% filter(mpg > 20)" ))
Verbatim text (no formatting)
cli_verbatim("This is displayed exactly as-is: {not interpolated}")
Lists
Unordered list
cli_ul() cli_li("First item") cli_li("Second item") cli_end()
Ordered list
cli_ol() cli_li("First step") cli_li("Second step") cli_end()
Definition list
cli_dl() cli_li(c(name = "The name field")) cli_li(c(email = "The email address")) cli_end()
Common Workflows
Base R to CLI Migration
Before: Base R error handling
validate_input <- function(x, y) { if (!is.numeric(x)) { stop("x must be numeric") } if (length(y) == 0) { stop("y cannot be empty") } if (length(x) != length(y)) { stop("x and y must have the same length") } }
After: CLI error handling
validate_input <- function(x, y) { if (!is.numeric(x)) { cli_abort(c( "{.arg x} must be numeric", "x" = "You supplied a {.cls {class(x)}} vector", "i" = "Use {.fn as.numeric} to convert" )) }
if (length(y) == 0) { cli_abort(c( "{.arg y} cannot be empty", "i" = "Provide at least one element" )) }
if (length(x) != length(y)) { cli_abort(c( "{.arg x} and {.arg y} must have the same length", "x" = "{.arg x} has length {length(x)}", "x" = "{.arg y} has length {length(y)}" )) } }
Error Message with Rich Context
check_required_columns <- function(data, required_cols) { actual_cols <- names(data) missing_cols <- setdiff(required_cols, actual_cols)
if (length(missing_cols) > 0) { cli_abort(c( "Required column{?s} missing from data", "x" = "Missing {length(missing_cols)} column{?s}: {.field {missing_cols}}", "i" = "Data has {length(actual_cols)} column{?s}: {.field {actual_cols}}", "i" = "Add the missing column{?s} or check for typos" )) }
invisible(data) }
Function with Progress Bar
process_files <- function(files, verbose = TRUE) { n <- length(files)
if (verbose) { cli_progress_bar( format = "Processing {cli::pb_bar} {cli::pb_current}/{cli::pb_total} [{cli::pb_eta}]", total = n ) }
results <- vector("list", n)
for (i in seq_along(files)) { results[[i]] <- process_file(files[[i]])
if (verbose) {
cli_progress_update()
}
}
results }
Resources & Advanced Topics
Reference Files
references/inline-markup.md - Complete catalog of inline classes organized by category, advanced patterns, nesting rules, and real-world examples
references/conditions.md - Advanced error design patterns, rlang integration, testing with testthat snapshots, migration guide, and anti-patterns
references/progress.md - Nested progress bars, custom formats, all progress variables, parallel processing, Shiny integration, and debugging
references/themes.md - Complete theming system with CSS-like selectors, container functions, color palettes, custom themes, and accessibility
references/ansi-operations.md - ANSI string operations (align, columns, nchar, etc.), hyperlinks, color detection, testing CLI output, and troubleshooting
External Resources
-
cli package documentation
-
cli GitHub repository
-
Building a semantic CLI (article)
Related Packages
-
rlang - Condition handling and error objects integrate with cli
-
glue - String interpolation powers cli's {} syntax
-
testthat - Snapshot testing for cli output