CLI for R Packages When to Use What task: Display error with context and formatting use: with inline markup and bullet lists task: Show warning with formatting use: with inline markup task: Display informative message use: with inline markup task: Show progress for counted operations use: with total count task: Show simple progress steps use: with status messages task: Format code or function names use: or task: Format file paths use: task: Format package names use: task: Format variable names use: task: Format values use: task: Handle singular/plural text use: or with pluralization task: Cre…

} matches digits\")\n```\n\n**When to use:**\n- Literal string values\n- Pattern strings\n- Format strings\n- When `.val` formatting is too generic\n\n## Emphasis Classes\n\n### .emph\n\nEmphasis for important concepts or terms:\n\n```r\ncli_text(\"This function is {.emph deprecated}\")\ncli_text(\"The file is {.emph locked} by another process\")\ncli_inform(\"{.emph Note}: This may take several minutes\")\n```\n\n**Rendering:**\n- Typically italic or colored\n- Lighter emphasis than `.strong`\n\n### .strong\n\nStrong emphasis for critical information:\n\n```r\ncli_warn(\"{.strong Warning}: This action cannot be undone\")\ncli_text(\"This parameter is {.strong required}\")\ncli_abort(\"{.strong Error}: Database connection failed\")\n```\n\n**Rendering:**\n- Typically bold or brightly colored\n- Stronger emphasis than `.emph`\n\n## Documentation Classes\n\n### .help\n\nR help topic references:\n\n```r\ncli_text(\"See {.help stats::lm} for details\")\ncli_inform(\"More info: {.help base::sum}\")\n```\n\n**Features:**\n- Creates link to help topic in RStudio\n- Supports `package::topic` notation\n- Fallback to plain text in terminals\n\n### .topic\n\nGeneric topic or section references:\n\n```r\ncli_text(\"See {.topic 'Error Handling'} section\")\ncli_inform(\"Refer to {.topic 'Advanced Usage'}\")\n```\n\n**When to use:**\n- Internal documentation sections\n- Vignette sections\n- General topic references\n\n### .vignette\n\nVignette references:\n\n```r\ncli_text(\"Read {.vignette cli::semantic-cli} for examples\")\ncli_inform(\"See {.vignette dplyr::programming}\")\n```\n\n**Features:**\n- Links to package vignettes\n- Format: `package::vignette-name`\n\n### .run\n\nRunnable R code examples:\n\n```r\ncli_text(\"Try: {.run install.packages('cli')}\")\ncli_inform(\"Debug with: {.run options(error = recover)}\")\n```\n\n**Features:**\n- Creates executable link in RStudio\n- Click to run code in console\n- Formatted as code in terminals\n\n## Special Classes\n\n### .kbd\n\nKeyboard keys and shortcuts:\n\n```r\ncli_text(\"Press {.kbd Ctrl+C} to cancel\")\ncli_text(\"Use {.kbd Enter} to confirm\")\ncli_inform(\"Save with {.kbd Cmd+S} (Mac) or {.kbd Ctrl+S} (Windows)\")\n```\n\n**Rendering:**\n- Typically in keyboard key style\n- May show as boxed or distinct formatting\n\n### .key\n\nAlternative to `.kbd` for key names:\n\n```r\ncli_text(\"Press the {.key RETURN} key\")\ncli_text(\"Hold {.key SHIFT} while clicking\")\n```\n\n### .or\n\nLogical OR separator for alternatives:\n\n```r\ncli_text(\"Use {.val 'yes'} {.or} {.val 'no'}\")\ncli_abort(\"Type must be {.val 'auto'} {.or} {.val 'manual'}\")\n```\n\n**Rendering:**\n- Formats as \" or \" with appropriate styling\n- Maintains class formatting for surrounding elements\n\n### .pkg\n\nPackage names:\n\n```r\ncli_text(\"Install {.pkg dplyr} for data manipulation\")\ncli_inform(\"Loading {.pkg ggplot2}\")\ncli_abort(\"{.pkg httr2} is required but not installed\")\n```\n\n**Features:**\n- Distinct package name formatting\n- May include CRAN/GitHub links in supported environments\n\n### .dt\n\nDefinition term in definition lists:\n\n```r\ncli_dl(c(\n \"{.dt name}\" = \"User's full name\",\n \"{.dt email}\" = \"Contact email address\"\n))\n```\n\n**When to use:**\n- Definition list terms\n- Glossary entries\n- Key-value pair keys\n\n### .dd\n\nDefinition description in definition lists:\n\n```r\ncli_dl(c(\n \"name\" = \"{.dd User's full name}\",\n \"email\" = \"{.dd Contact email address}\"\n))\n```\n\n**When to use:**\n- Definition list descriptions\n- Glossary definitions\n- Key-value pair values\n\n## Vector Collapsing\n\nVectors are automatically collapsed with appropriate separators:\n\n### Default Behavior\n\n```r\npkgs \u003c- c(\"dplyr\", \"tidyr\", \"ggplot2\")\ncli_text(\"Loading packages: {.pkg {pkgs}}\")\n#> Loading packages: dplyr, tidyr, and ggplot2\n\nfiles \u003c- c(\"a.R\", \"b.R\")\ncli_text(\"Modified: {.file {files}}\")\n#> Modified: a.R and b.R\n\nsingle \u003c- \"data.csv\"\ncli_text(\"Found: {.file {single}}\")\n#> Found: data.csv\n```\n\n**Rules:**\n- Length 1: no separators\n- Length 2: \" and \" separator\n- Length 3+: \", \" separators with \" and \" before last item\n\n### Collapsing Empty Vectors\n\n```r\nempty \u003c- character()\ncli_text(\"Files: {.file {empty}}\")\n#> Files:\n\ncli_text(\"Files: {?none/one/some}: {.file {empty}}\")\n#> Files: none:\n```\n\n### Custom Collapse Separators\n\nControl collapsing with glue transformers:\n\n```r\nitems \u003c- c(\"apple\", \"banana\", \"cherry\")\n\n# Custom separator\ncli_text(\"Items: {.val {items}}\", .transformer = function(text, envir) {\n glue::glue_collapse(text, sep = \" | \", last = \" | \")\n})\n#> Items: 'apple' | 'banana' | 'cherry'\n\n# OR separator\ncli_text(\"Choose: {.val {items}}\", .transformer = function(text, envir) {\n glue::glue_collapse(text, sep = \", \", last = \" or \")\n})\n#> Choose: 'apple', 'banana' or 'cherry'\n```\n\n### Truncating Long Vectors\n\n```r\nmany \u003c- letters[1:20]\n\ncli_text(\"Variables: {.var {many}}\")\n#> Variables: a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, and t\n\n# Truncate with custom transformer\ncli_text(\"Variables: {.var {head(many, 5)}} and {length(many) - 5} more\")\n#> Variables: a, b, c, d, e and 15 more\n```\n\n## Advanced Patterns\n\n### Nested Markup\n\nClasses can be nested for combined formatting:\n\n```r\ncli_text(\"Call {.fn {.pkg dplyr}::filter} to subset data\")\ncli_abort(\"File {.file {.path {dir}}/data.csv} not found\")\ncli_text(\"Set {.arg timeout} to {.val {default_timeout}} by default\")\n```\n\n**Nesting guidelines:**\n- Outer class determines primary formatting\n- Inner classes add semantic meaning\n- Keep nesting depth reasonable (2-3 levels max)\n\n### Combining with Glue Expressions\n\n```r\nn \u003c- 5\ncli_text(\"Processing {n} file{?s} from {.path {getwd()}}\")\n\nstatus \u003c- \"complete\"\ncli_inform(\"Status: {.val {toupper(status)}}\")\n\npkg \u003c- \"dplyr\"\ncli_text(\"Version: {.pkg {pkg}} {.val {packageVersion(pkg)}}\")\n```\n\n### Custom Glue Transformers\n\nFull control over formatting with transformers:\n\n```r\n# Highlight differences\ncli_text(\n \"Expected {.val {expected}}, got {.val {actual}}\",\n .transformer = function(text, envir) {\n # Custom logic here\n text\n }\n)\n\n# Add prefixes\nmy_transformer \u003c- function(text, envir) {\n if (startsWith(text, \".pkg\")) {\n paste0(\"R package: \", glue::glue(text, .envir = envir))\n } else {\n glue::identity_transformer(text, envir)\n }\n}\n\ncli_text(\"Install {.pkg cli}\", .transformer = my_transformer)\n```\n\n### Conditional Formatting\n\n```r\nstatus \u003c- \"error\"\ncli_text(\"Status: {.{if(status == 'error') 'strong' else 'emph'} {status}}\")\n\ntype \u003c- class(x)\ncli_text(\"Type: {if(is.numeric(x)) .val else .cls} {type}\")\n```\n\n### Multiple Classes on Same Content\n\nCombine semantic classes:\n\n```r\n# Package function\ncli_text(\"{.fn {.pkg stats}::median}\")\n\n# File in path\ncli_text(\"{.file {.path /usr/local}/script.R}\")\n\n# Variable value\ncli_text(\"{.var x} = {.val {x}}\")\n```\n\n### Working with Lists\n\n```r\nconfig \u003c- list(host = \"localhost\", port = 8080, ssl = TRUE)\n\ncli_dl(c(\n \"{.field host}\" = \"{.val {config$host}}\",\n \"{.field port}\" = \"{.val {config$port}}\",\n \"{.field ssl}\" = \"{.val {config$ssl}}\"\n))\n```\n\n### Escaping Markup\n\nPrevent interpretation with double braces:\n\n```r\ncli_text(\"In glue, use {{variable}} syntax\")\n#> In glue, use {variable} syntax\n\ncli_text(\"Literal: {{.code not_markup}}\")\n#> Literal: {.code not_markup}\n```\n\n## Pluralization\n\nPluralization adapts text based on quantities using `{?}` syntax.\n\n### Single Alternative Pattern\n\nAdd \"s\" for plural:\n\n```r\nn \u003c- 1\ncli_text(\"{n} file{?s} found\")\n#> 1 file found\n\nn \u003c- 5\ncli_text(\"{n} file{?s} found\")\n#> 5 files found\n```\n\n### Two Alternative Pattern\n\nSpecify singular/plural forms:\n\n```r\nn \u003c- 1\ncli_text(\"{n} director{?y/ies}\")\n#> 1 directory\n\nn \u003c- 3\ncli_text(\"{n} director{?y/ies}\")\n#> 3 directories\n```\n\n**Common patterns:**\n- `{?y/ies}` - directory/directories\n- `{?/s}` - item/items\n- `{?is/are}` - is/are\n- `{?/es}` - box/boxes\n- `{?ex/ices}` - index/indices\n\n### Three Alternative Pattern\n\nHandle zero/one/many:\n\n```r\nn \u003c- 0\ncli_text(\"{?No/One/Some} file{?s} {?is/is/are} ready\")\n#> No files are ready\n\nn \u003c- 1\ncli_text(\"{?No/One/Some} file{?s} {?is/is/are} ready\")\n#> One file is ready\n\nn \u003c- 5\ncli_text(\"{?No/One/Some} file{?s} {?is/is/are} ready\")\n#> Some files are ready\n```\n\n### Setting Quantity with qty()\n\nControl pluralization explicitly:\n\n```r\nupdated \u003c- 3\ntotal \u003c- 10\ncli_text(\"{updated}/{total} {qty(updated)} file{?s} {?needs/need} update{?s}\")\n#> 3/10 files need updates\n```\n\n**When to use qty():**\n- When quantity appears elsewhere\n- Complex expressions with multiple numbers\n- Explicit pluralization control\n\n### no() Helper\n\nDisplay \"no\" instead of 0:\n\n```r\nn \u003c- 0\ncli_text(\"Found {no(n)} error{?s}\")\n#> Found no errors\n\nn \u003c- 3\ncli_text(\"Found {no(n)} error{?s}\")\n#> Found 3 errors\n```\n\n### Advanced Pluralization Patterns\n\n**Multiple quantities in one message:**\n\n```r\nnerr \u003c- 2\nnwarn \u003c- 1\ncli_text(\n \"{nerr} error{?s} and {nwarn} {qty(nwarn)} warning{?s} found\"\n)\n#> 2 errors and 1 warning found\n```\n\n**Conditional articles:**\n\n```r\nn \u003c- 1\ncli_text(\"Found {?a /}{n} file{?s}\")\n#> Found a 1 file\n\nn \u003c- 5\ncli_text(\"Found {?a /}{n} file{?s}\")\n#> Found 5 files\n```\n\n**Complex pluralization:**\n\n```r\nnfile \u003c- 3\nndir \u003c- 1\ncli_text(\n \"{nfile} file{?s} in {ndir} {qty(ndir)} director{?y/ies}\"\n)\n#> 3 files in 1 directory\n```\n\n**Verb agreement:**\n\n```r\nn \u003c- 1\ncli_text(\"File {?was/were} modified\")\n#> File was modified\n\nn \u003c- 3\ncli_text(\"Files {?was/were} modified\")\n#> Files were modified\n```\n\n**Possessives:**\n\n```r\nn \u003c- 1\ncli_text(\"User{?'s/'s'} setting{?s}\")\n#> User's setting\n\nn \u003c- 3\ncli_text(\"Users{?'s/'s'} settings\")\n#> Users' settings\n```\n\n## Performance Considerations\n\n### High-Frequency Messages\n\nFor loops with many iterations:\n\n```r\n# Expensive - creates cli context each iteration\nfor (i in 1:10000) {\n cli_text(\"Processing {.val {i}}\")\n}\n\n# Better - batch messages\nif (i %% 1000 == 0) {\n cli_text(\"Processed {.val {i}} items\")\n}\n\n# Best - use progress bar\ncli_progress_bar(\"Processing\", total = 10000)\nfor (i in 1:10000) {\n # work here\n cli_progress_update()\n}\n```\n\n### Expensive Interpolation\n\nAvoid expensive computations in markup:\n\n```r\n# Expensive - computes every time\ncli_text(\"Processing {.file {slow_path_computation()}}\")\n\n# Better - compute once\npath \u003c- slow_path_computation()\ncli_text(\"Processing {.file {path}}\")\n```\n\n### Conditional Messages\n\nUse conditions to avoid unnecessary formatting:\n\n```r\n# Inefficient\nif (verbose) cli_text(\"Status: {.val {expensive_status_check()}}\")\n\n# Better\nif (verbose) {\n status \u003c- expensive_status_check()\n cli_text(\"Status: {.val {status}}\")\n}\n```\n\n### Large Vectors\n\nTruncate large vectors before formatting:\n\n```r\n# Problematic with huge vectors\nvars \u003c- names(huge_dataframe)\ncli_text(\"Variables: {.var {vars}}\")\n\n# Better\nn_vars \u003c- length(vars)\nif (n_vars > 10) {\n cli_text(\"Variables: {.var {head(vars, 10)}} and {n_vars - 10} more\")\n} else {\n cli_text(\"Variables: {.var {vars}}\")\n}\n```\n\n## Quick Reference Table\n\n| Class | Use For | Example | Output |\n|-------|---------|---------|--------|\n| `.code` | Code expressions | `{.code sum(x)}` | `sum(x)` |\n| `.fn`, `.fun` | Functions | `{.fn mean}` | `mean()` |\n| `.arg` | Function arguments | `{.arg na.rm}` | `na.rm` |\n| `.cls` | Class names | `{.cls data.frame}` | data.frame |\n| `.type` | Base types | `{.type integer}` | integer |\n| `.obj_type_friendly` | Friendly types | `{.obj_type_friendly {x}}` | an integer vector |\n| `.file` | File names | `{.file script.R}` | script.R |\n| `.path` | Directory paths | `{.path /usr/local}` | /usr/local |\n| `.email` | Email addresses | `{.email [email protected]}` | [email protected] |\n| `.url` | Web URLs | `{.url https://example.com}` | https://example.com |\n| `.href` | Custom links | `{.href [text](url)}` | text (linked) |\n| `.val` | Data values | `{.val {x}}` | 42 or 'text' |\n| `.var` | Variable names | `{.var column}` | column |\n| `.envvar` | Environment vars | `{.envvar PATH}` | PATH |\n| `.field` | Object fields | `{.field name}` | name |\n| `.str` | String literals | `{.str 'pattern'}` | 'pattern' |\n| `.emph` | Emphasis | `{.emph important}` | *important* |\n| `.strong` | Strong emphasis | `{.strong critical}` | **critical** |\n| `.help` | Help topics | `{.help stats::lm}` | stats::lm (linked) |\n| `.topic` | Topic references | `{.topic 'Intro'}` | 'Intro' |\n| `.vignette` | Vignettes | `{.vignette pkg::name}` | pkg::name (linked) |\n| `.run` | Runnable code | `{.run code}` | code (executable) |\n| `.kbd`, `.key` | Keyboard keys | `{.kbd Ctrl+C}` | Ctrl+C |\n| `.or` | Logical OR | `{.val x} {.or} {.val y}` | x or y |\n| `.pkg` | Package names | `{.pkg dplyr}` | dplyr |\n| `.dt` | Definition term | `{.dt term}` | term (in definition list) |\n| `.dd` | Definition desc | `{.dd description}` | description (in definition list) |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":19774,"content_sha256":"1ed5cb03a034ffa5776c781ae0215f734e1bd8556fe3551470a5dd1e884216ce"},{"filename":"references/progress.md","content":"# Progress Indicators\n\n## Table of Contents\n\n1. [Choosing Progress Style](#choosing-progress-style)\n2. [cli_progress_bar() Deep Dive](#cli_progress_bar-deep-dive)\n3. [cli_progress_step() Advanced](#cli_progress_step-advanced)\n4. [cli_progress_message()](#cli_progress_message)\n5. [Progress Variables Reference](#progress-variables-reference)\n6. [Format Strings](#format-strings)\n7. [Progress Styles](#progress-styles)\n8. [Advanced Scenarios](#advanced-scenarios)\n9. [Shiny Integration](#shiny-integration)\n10. [C-Level Progress](#c-level-progress)\n11. [Debugging Progress](#debugging-progress)\n12. [Performance Considerations](#performance-considerations)\n\n## Choosing Progress Style\n\ncli offers three progress indicator functions, each suited for different scenarios:\n\n### cli_progress_bar()\n\nUse when:\n- You know the total number of iterations upfront\n- Progress can be measured as a percentage\n- Operation involves loops or iterating over collections\n- Users need ETA and completion estimates\n\n```r\n# Good use case\nprocess_files \u003c- function(files) {\n cli_progress_bar(\"Processing\", total = length(files))\n for (file in files) {\n process(file)\n cli_progress_update()\n }\n}\n```\n\n### cli_progress_step()\n\nUse when:\n- Progress happens in discrete named steps\n- Total work is unknown or variable\n- Steps have different durations\n- You want automatic success/failure indicators\n\n```r\n# Good use case\ndeploy_app \u003c- function() {\n cli_progress_step(\"Building assets\")\n build_assets()\n\n cli_progress_step(\"Running tests\")\n run_tests()\n\n cli_progress_step(\"Deploying to server\")\n deploy()\n}\n```\n\n### cli_progress_message()\n\nUse when:\n- Showing a simple status message\n- No quantifiable progress to track\n- Operation duration is uncertain\n- Want a spinner without step semantics\n\n```r\n# Good use case\ncli_progress_message(\"Waiting for API response...\")\nresponse \u003c- long_running_api_call()\n```\n\n## cli_progress_bar() Deep Dive\n\n### Basic Parameters\n\n```r\ncli_progress_bar(\n name = NULL, # Progress bar name/label\n status = NULL, # Additional status text\n type = \"iterator\", # Bar type: \"iterator\", \"tasks\", \"download\", \"custom\"\n total = NA, # Total number of items (NA for unknown)\n format = NULL, # Custom format string\n format_done = NULL, # Format when complete\n format_failed = NULL, # Format when failed\n clear = TRUE, # Clear bar when done\n current = TRUE, # Show current position\n auto_terminate = TRUE, # Auto-close when function exits\n .auto_close = TRUE # Deprecated, use auto_terminate\n)\n```\n\n### Type Parameter Examples\n\n```r\n# Iterator (default) - for loops\ncli_progress_bar(\"Processing items\", total = 100, type = \"iterator\")\n\n# Tasks - for discrete tasks\ncli_progress_bar(\"Completing tasks\", total = 5, type = \"tasks\")\n\n# Download - for file downloads\ncli_progress_bar(\"Downloading\", total = file_size, type = \"download\")\n\n# Custom - for user-defined formats\ncli_progress_bar(\n format = \"Working {cli::pb_bar} {cli::pb_percent}\",\n total = 100,\n type = \"custom\"\n)\n```\n\n### Auto-Termination Behavior\n\nProgress bars automatically close when the calling function exits:\n\n```r\nprocess_data \u003c- function(data) {\n cli_progress_bar(\"Processing\", total = nrow(data))\n\n for (i in seq_len(nrow(data))) {\n process_row(data[i, ])\n cli_progress_update()\n }\n\n # Bar automatically closes here when function returns\n}\n\n# Manual control if needed\nprocess_data_manual \u003c- function(data) {\n id \u003c- cli_progress_bar(\"Processing\", total = nrow(data))\n\n for (i in seq_len(nrow(data))) {\n process_row(data[i, ])\n cli_progress_update(id = id)\n }\n\n cli_progress_done(id = id) # Explicit close\n}\n```\n\n### Clearing Behavior\n\nControl whether progress bars remain visible after completion:\n\n```r\n# Clear after completion (default)\ncli_progress_bar(\"Working\", total = 100, clear = TRUE)\nfor (i in 1:100) {\n Sys.sleep(0.01)\n cli_progress_update()\n}\n# Bar disappears when done\n\n# Keep visible after completion\ncli_progress_bar(\"Working\", total = 100, clear = FALSE)\nfor (i in 1:100) {\n Sys.sleep(0.01)\n cli_progress_update()\n}\n# Bar remains showing 100% completion\n```\n\n### Dynamic Updates\n\nUpdate progress with optional status messages:\n\n```r\nprocess_files \u003c- function(files) {\n cli_progress_bar(\"Processing files\", total = length(files))\n\n for (i in seq_along(files)) {\n file \u003c- files[[i]]\n\n # Update with status\n cli_progress_update(\n status = sprintf(\"Current: %s\", basename(file))\n )\n\n process_file(file)\n }\n}\n\n# Update multiple items at once\ncli_progress_update(inc = 5) # Increment by 5\ncli_progress_update(set = 50) # Set to specific value\n```\n\n## Format Strings\n\n### Default Formats\n\n```r\n# Iterator format (default)\n# \"{cli::pb_spin} {cli::pb_name} {cli::pb_bar} {cli::pb_percent} | ETA: {cli::pb_eta}\"\n\n# Download format\n# \"Downloaded {cli::pb_current_bytes}/{cli::pb_total_bytes} ({cli::pb_rate_bytes}/s) | ETA: {cli::pb_eta}\"\n\n# Tasks format\n# \"{cli::pb_name} {cli::pb_current}/{cli::pb_total} | ETA: {cli::pb_eta}\"\n```\n\n### Custom Format Examples\n\n```r\n# Simple percentage only\ncli_progress_bar(\n \"Working\",\n total = 100,\n format = \"{cli::pb_name} {cli::pb_percent}\"\n)\n\n# With ETA and rate\ncli_progress_bar(\n \"Processing\",\n total = 1000,\n format = \"{cli::pb_bar} {cli::pb_current}/{cli::pb_total} [{cli::pb_eta} @ {cli::pb_rate}]\"\n)\n\n# With elapsed time\ncli_progress_bar(\n \"Running\",\n total = 50,\n format = \"{cli::pb_spin} {cli::pb_name} {cli::pb_percent} | Elapsed: {cli::pb_elapsed}\"\n)\n\n# Download-style with bytes\ncli_progress_bar(\n \"Downloading data.zip\",\n total = 1024^3, # 1 GB\n format = paste0(\n \"{cli::pb_current_bytes}/{cli::pb_total_bytes} \",\n \"({cli::pb_percent}) | \",\n \"{cli::pb_rate_bytes}/s | \",\n \"ETA: {cli::pb_eta}\"\n )\n)\n\n# Custom status display\ncli_progress_bar(\n format = \"{cli::pb_bar} {cli::pb_status}\"\n)\n```\n\n### Completion and Failure Formats\n\n```r\ncli_progress_bar(\n \"Processing\",\n total = 100,\n format = \"Working: {cli::pb_bar} {cli::pb_percent}\",\n format_done = \"Completed {cli::pb_total} items in {cli::pb_elapsed}\",\n format_failed = \"Failed after processing {cli::pb_current} items\"\n)\n\n# Trigger failure\nfor (i in 1:50) {\n if (i == 30) {\n cli_progress_done(result = \"failed\")\n break\n }\n cli_progress_update()\n}\n```\n\n## Progress Variables Reference\n\n### Basic Progress Variables\n\n```r\n# Current position\ncli::pb_current # Current iteration number (e.g., 45)\n\n# Total work\ncli::pb_total # Total iterations (e.g., 100)\n\n# Percentage\ncli::pb_percent # Completion percentage (e.g., \"45%\")\n\n# Visual bar\ncli::pb_bar # Progress bar visualization (e.g., \"=========> \")\n\n# Spinner\ncli::pb_spin # Animated spinner character\n\n# Name and status\ncli::pb_name # Progress bar name\ncli::pb_status # Current status message\n```\n\n### Timing Variables\n\n```r\n# Time elapsed\ncli::pb_elapsed # Time since start (e.g., \"2m 30s\")\ncli::pb_elapsed_raw # Elapsed time in seconds (e.g., 150.234)\ncli::pb_elapsed_clock # Clock time format (e.g., \"02:30\")\n\n# Time remaining\ncli::pb_eta # Estimated time remaining (e.g., \"1m 15s\")\ncli::pb_eta_raw # ETA in seconds (e.g., 75.0)\ncli::pb_eta_str # ETA as string (e.g., \"ETA: 1m 15s\")\n\n# Rate\ncli::pb_rate # Items per second (e.g., \"0.6/s\")\ncli::pb_rate_raw # Raw rate value (e.g., 0.6)\n```\n\n### Byte-Based Variables\n\nFor download/upload progress bars:\n\n```r\n# Current bytes\ncli::pb_current_bytes # Formatted current (e.g., \"45.2 MB\")\n\n# Total bytes\ncli::pb_total_bytes # Formatted total (e.g., \"100 MB\")\n\n# Rate\ncli::pb_rate_bytes # Bytes per second (e.g., \"2.1 MB/s\")\n```\n\n### Advanced Variables\n\n```r\n# Tick information\ncli::pb_tick # Current tick number\ncli::pb_tick_rate # Ticks per second\n\n# Timestamps\ncli::pb_start # Start timestamp (POSIXct)\ncli::pb_timestamp # Current timestamp (POSIXct)\n\n# Extra data\ncli::pb_extra # User-defined extra data\n```\n\n## cli_progress_step() Advanced\n\n### Basic Usage\n\n```r\ndeploy_package \u003c- function() {\n cli_progress_step(\"Checking package\")\n check_result \u003c- check_package()\n\n cli_progress_step(\"Building package\")\n build_result \u003c- build_package()\n\n cli_progress_step(\"Uploading to CRAN\")\n upload_result \u003c- upload_package()\n}\n```\n\n### Spinner Customization\n\n```r\n# Use different spinner style\noptions(cli.spinner = \"dots\")\ncli_progress_step(\"Processing\")\n\n# Available spinners\noptions(cli.spinner = \"line\") # Classic line spinner\noptions(cli.spinner = \"dots\") # Dots\noptions(cli.spinner = \"dots2\") # Alternative dots\noptions(cli.spinner = \"dots3\") # More dots\noptions(cli.spinner = \"dots12\") # 12-frame dots\noptions(cli.spinner = \"arrow\") # Rotating arrow\noptions(cli.spinner = \"bouncingBar\") # Bouncing bar\noptions(cli.spinner = \"clock\") # Clock hands\n\n# Custom spinner frames\noptions(cli.spinner = list(\n interval = 100,\n frames = c(\"◐\", \"◓\", \"◑\", \"◒\")\n))\n```\n\n### Multiple Concurrent Steps\n\nProgress steps can be nested:\n\n```r\nprocess_projects \u003c- function(projects) {\n cli_progress_step(\"Processing {length(projects)} projects\")\n\n for (proj in projects) {\n cli_progress_step(\"Building {proj}\")\n build_project(proj)\n\n cli_progress_step(\"Testing {proj}\")\n test_project(proj)\n\n cli_progress_step(\"Deploying {proj}\")\n deploy_project(proj)\n }\n}\n```\n\n### Dynamic Message Updates\n\nUpdate step messages while they're running:\n\n```r\nprocess_with_details \u003c- function(files) {\n id \u003c- cli_progress_step(\"Processing files\")\n\n for (i in seq_along(files)) {\n cli_progress_update(\n id = id,\n status = sprintf(\"File %d/%d: %s\", i, length(files), basename(files[i]))\n )\n\n process_file(files[i])\n }\n\n cli_progress_done(id = id)\n}\n```\n\n### Success/Failure Termination\n\n```r\ndeploy_with_status \u003c- function() {\n tryCatch({\n cli_progress_step(\"Building application\")\n build_app()\n\n cli_progress_step(\"Running tests\")\n test_result \u003c- run_tests()\n\n if (!test_result$passed) {\n cli_progress_done(result = \"failed\", msg_failed = \"Tests failed!\")\n return(FALSE)\n }\n\n cli_progress_step(\"Deploying\")\n deploy()\n\n cli_progress_done(result = \"done\", msg_done = \"Deployment successful!\")\n TRUE\n\n }, error = function(e) {\n cli_progress_done(result = \"failed\", msg_failed = \"Deployment failed: {e$message}\")\n FALSE\n })\n}\n```\n\n### Custom Success/Failure Messages\n\n```r\nid \u003c- cli_progress_step(\n \"Processing data\",\n msg_done = \"Data processed successfully\",\n msg_failed = \"Data processing failed\"\n)\n\n# Mark as done\ncli_progress_done(id = id, result = \"done\")\n\n# Or mark as failed\ncli_progress_done(id = id, result = \"failed\")\n```\n\n## cli_progress_message()\n\nSimple progress messages with automatic spinners:\n\n```r\n# Basic usage\ncli_progress_message(\"Loading configuration...\")\nconfig \u003c- load_config()\n\n# With explicit cleanup\nid \u003c- cli_progress_message(\"Waiting for server...\")\nwait_for_server()\ncli_progress_done(id = id)\n\n# Updating message\nid \u003c- cli_progress_message(\"Connecting...\")\ncli_progress_update(id = id, status = \"Authenticating...\")\ncli_progress_update(id = id, status = \"Connected!\")\ncli_progress_done(id = id)\n```\n\n## Progress Styles\n\ncli includes several built-in progress bar styles:\n\n```r\n# Classic style (default)\noptions(cli.progress_bar_style = \"classic\")\n\n# Unicode style (requires Unicode support)\noptions(cli.progress_bar_style = \"unicode\")\n\n# ASCII style (for limited terminals)\noptions(cli.progress_bar_style = \"ascii\")\n\n# Custom style\noptions(cli.progress_bar_style = list(\n complete = \"=\",\n incomplete = \" \",\n current = \">\",\n width = 40\n))\n```\n\n## Advanced Scenarios\n\n### Nested Progress Bars\n\n```r\nprocess_datasets \u003c- function(datasets) {\n cli_progress_bar(\"Processing datasets\", total = length(datasets))\n\n for (dataset in datasets) {\n # Outer progress updates\n cli_progress_update()\n\n # Inner progress bar\n n_rows \u003c- nrow(dataset)\n cli_progress_bar(\"Processing rows\", total = n_rows)\n\n for (i in seq_len(n_rows)) {\n process_row(dataset[i, ])\n cli_progress_update() # Updates inner bar\n }\n # Inner bar auto-closes\n }\n # Outer bar auto-closes\n}\n```\n\n### Progress with Parallel Code\n\nProgress bars in parallel contexts require special handling:\n\n```r\nlibrary(foreach)\nlibrary(doParallel)\n\n# Not recommended - doesn't work well\nprocess_parallel_bad \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n foreach(item = items) %dopar% {\n result \u003c- process_item(item)\n cli_progress_update() # Won't work across processes\n result\n }\n}\n\n# Better approach - update after each completion\nprocess_parallel_better \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n results \u003c- foreach(item = items) %dopar% {\n process_item(item)\n }\n\n # Update in main thread\n for (i in seq_along(results)) {\n cli_progress_update()\n }\n\n results\n}\n\n# Best approach - use progress updates between parallel batches\nprocess_parallel_best \u003c- function(items, batch_size = 10) {\n batches \u003c- split(items, ceiling(seq_along(items) / batch_size))\n cli_progress_bar(\"Processing\", total = length(items))\n\n results \u003c- list()\n for (batch in batches) {\n batch_results \u003c- foreach(item = batch) %dopar% {\n process_item(item)\n }\n results \u003c- c(results, batch_results)\n cli_progress_update(inc = length(batch))\n }\n\n results\n}\n```\n\n### Progress in Loops vs Mapping\n\n```r\n# Traditional loop with progress\nprocess_loop \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n results \u003c- vector(\"list\", length(items))\n for (i in seq_along(items)) {\n results[[i]] \u003c- process_item(items[[i]])\n cli_progress_update()\n }\n\n results\n}\n\n# Using cli_progress_along()\nprocess_along \u003c- function(items) {\n results \u003c- lapply(cli_progress_along(items, \"Processing\"), function(i) {\n process_item(items[[i]])\n })\n\n results\n}\n\n# Using purrr with progress\nlibrary(purrr)\nprocess_purrr \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n map(items, function(item) {\n result \u003c- process_item(item)\n cli_progress_update()\n result\n })\n}\n```\n\n### Progress Output vs Regular CLI Output\n\nProgress bars interact with other cli output:\n\n```r\nprocess_with_messages \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n for (i in seq_along(items)) {\n result \u003c- process_item(items[[i]])\n\n # Regular cli messages work alongside progress\n if (result$warnings) {\n cli_alert_warning(\"Item {i} had warnings\")\n }\n\n cli_progress_update()\n }\n\n cli_alert_success(\"Processing complete\")\n}\n\n# Progress-aware output functions\nprocess_with_progress_output \u003c- function(items) {\n cli_progress_bar(\"Processing\", total = length(items))\n\n for (item in items) {\n # These respect progress bars\n cli_progress_output(cli_text(\"Processing {item}\"))\n process_item(item)\n cli_progress_update()\n }\n}\n```\n\n### Unknown Total\n\nProgress bars can run without knowing the total:\n\n```r\nprocess_stream \u003c- function(conn) {\n cli_progress_bar(\"Processing stream\", total = NA)\n\n while (length(line \u003c- readLines(conn, n = 1)) > 0) {\n process_line(line)\n cli_progress_update()\n }\n\n # Shows spinner and count, no percentage\n}\n\n# Update total when it becomes known\nprocess_dynamic \u003c- function() {\n cli_progress_bar(\"Discovering files\", total = NA)\n\n files \u003c- find_files()\n\n # Update total\n cli_progress_update(total = length(files))\n\n for (file in files) {\n process_file(file)\n cli_progress_update()\n }\n}\n```\n\n## Shiny Integration\n\nProgress indicators in Shiny applications require special consideration:\n\n```r\nlibrary(shiny)\n\nui \u003c- fluidPage(\n actionButton(\"process\", \"Process Data\")\n)\n\nserver \u003c- function(input, output, session) {\n observeEvent(input$process, {\n # Use Shiny's progress API\n withProgress(message = \"Processing\", {\n\n # cli progress works but won't show in Shiny UI\n cli_progress_bar(\"Internal progress\", total = 100)\n\n for (i in 1:100) {\n Sys.sleep(0.01)\n\n # Update Shiny progress\n incProgress(1/100)\n\n # cli progress (for logs)\n cli_progress_update()\n }\n })\n })\n}\n\n# Better: Use cli in Shiny with proper output\nserver \u003c- function(input, output, session) {\n observeEvent(input$process, {\n # Redirect cli output to console\n withProgress(message = \"Processing\", {\n # cli progress shows in R console, not Shiny UI\n process_data()\n })\n })\n}\n```\n\n## C-Level Progress\n\nFor package developers using C/C++:\n\n```r\n# In R\nprocess_with_c \u003c- function(data) {\n cli_progress_bar(\"Processing in C\", total = nrow(data))\n .Call(C_process_data, data, environment())\n}\n\n# In C code (using cli's C API)\n# #include \u003ccli/progress.h>\n#\n# SEXP C_process_data(SEXP data, SEXP progress_env) {\n# R_xlen_t n = Rf_xlength(data);\n#\n# for (R_xlen_t i = 0; i \u003c n; i++) {\n# // Process data[i]\n#\n# // Update progress from C\n# cli_progress_update(progress_env);\n# }\n#\n# return R_NilValue;\n# }\n```\n\nNote: C-level progress requires the cli package C headers and proper linking.\n\n## Debugging Progress\n\n### Common Issues\n\n```r\n# Issue: Progress bar not updating\n# Problem: Not calling cli_progress_update()\nfor (i in 1:100) {\n process(i)\n # Missing: cli_progress_update()\n}\n\n# Issue: Multiple progress bars interfering\n# Solution: Store IDs and update correct bar\nid1 \u003c- cli_progress_bar(\"Task 1\", total = 10)\nid2 \u003c- cli_progress_bar(\"Task 2\", total = 20)\n\ncli_progress_update(id = id1)\ncli_progress_update(id = id2)\n\n# Issue: Progress bar not visible\n# Check: Is stderr redirected? Is output captured?\n# Progress bars use stderr by default\n\n# Issue: Progress bar flickers\n# Problem: Output mixing with progress\n# Solution: Use cli_progress_output()\ncli_progress_output(cli_alert_info(\"Status update\"))\n```\n\n### Testing Progress Bars\n\n```r\ntest_that(\"progress bar updates correctly\", {\n # Mock progress to test without output\n mockery::stub(my_function, \"cli_progress_bar\", NULL)\n mockery::stub(my_function, \"cli_progress_update\", NULL)\n\n result \u003c- my_function()\n expect_true(result)\n})\n\n# Test with captured output\ntest_that(\"progress messages are correct\", {\n output \u003c- capture.output({\n process_data(test_data)\n })\n\n # Note: Progress bars may not appear in captured output\n # Consider testing function logic separately\n})\n```\n\n### Disabling Progress\n\n```r\n# Disable all progress indicators\noptions(cli.progress_show_after = Inf)\n\n# Or use show_after parameter\ncli_progress_bar(\"Working\", total = 100, show_after = Inf)\n\n# Conditional progress\nprocess_data \u003c- function(data, verbose = TRUE) {\n if (verbose) {\n cli_progress_bar(\"Processing\", total = nrow(data))\n }\n\n for (i in seq_len(nrow(data))) {\n process_row(data[i, ])\n if (verbose) cli_progress_update()\n }\n}\n```\n\n## Performance Considerations\n\n### Update Frequency\n\n```r\n# Bad: Update too frequently (slows down loop)\nfor (i in 1:1e6) {\n fast_operation(i)\n cli_progress_update() # 1 million updates!\n}\n\n# Better: Update periodically\ncli_progress_bar(\"Processing\", total = 1e6)\nfor (i in 1:1e6) {\n fast_operation(i)\n if (i %% 1000 == 0) cli_progress_update(set = i)\n}\n\n# Best: Use show_after to delay display\ncli_progress_bar(\n \"Processing\",\n total = 1e6,\n show_after = 2 # Only show if takes >2 seconds\n)\n```\n\n### Overhead\n\nProgress bars add minimal overhead, but consider:\n\n```r\n# Negligible overhead for slow operations\nprocess_files \u003c- function(files) {\n cli_progress_bar(\"Processing\", total = length(files))\n for (file in files) {\n slow_operation(file) # 1 second per file\n cli_progress_update() # \u003c1ms overhead\n }\n}\n\n# Noticeable overhead for fast operations\nprocess_numbers \u003c- function(n = 1e6) {\n cli_progress_bar(\"Processing\", total = n)\n for (i in 1:n) {\n fast_operation(i) # 1μs per operation\n cli_progress_update() # 1ms overhead - 1000x slower!\n }\n}\n\n# Solution: Batch updates\nprocess_numbers_fast \u003c- function(n = 1e6, update_every = 1000) {\n cli_progress_bar(\"Processing\", total = n)\n for (i in 1:n) {\n fast_operation(i)\n if (i %% update_every == 0) {\n cli_progress_update(set = i)\n }\n }\n}\n```\n\n### Terminal Performance\n\n```r\n# Progress bars can be slow on Windows\n# Use ASCII style for better performance\nif (.Platform$OS.type == \"windows\") {\n options(cli.progress_bar_style = \"ascii\")\n}\n\n# Disable Unicode for remote sessions\nif (Sys.getenv(\"SSH_CONNECTION\") != \"\") {\n options(cli.unicode = FALSE)\n}\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":20545,"content_sha256":"d79980825ff26fa36cb694e12874720945d3d6c28cbc04805db6ebb3fdf996f9"},{"filename":"references/themes.md","content":"# CLI Themes and Styling\n\n## Table of Contents\n\n- [Theme Basics](#theme-basics)\n- [Container Functions](#container-functions)\n- [Selector Types](#selector-types)\n- [Theme Properties](#theme-properties)\n- [Built-in Themes](#built-in-themes)\n- [Custom Themes](#custom-themes)\n- [App and Package Themes](#app-and-package-themes)\n- [Color Palettes](#color-palettes)\n- [Accessibility](#accessibility)\n- [Debugging Themes](#debugging-themes)\n\n## Theme Basics\n\nCLI uses a CSS-like theming system to style console output. Themes consist of selectors that match elements and properties that define their appearance.\n\n### How Themes Work\n\n1. Elements are identified by selectors (like `.alert-success` or `.code`)\n2. Selectors are matched against the element hierarchy\n3. Properties are applied to matched elements\n4. Properties cascade through the element tree\n\n### Basic Theme Structure\n\n```r\nmy_theme \u003c- list(\n \".alert-success\" = list(\n \"color\" = \"green\",\n \"font-weight\" = \"bold\"\n ),\n \".code\" = list(\n \"color\" = \"blue\",\n \"background-color\" = \"grey90\"\n )\n)\n\ncli_div(theme = my_theme)\ncli_alert_success(\"Operation completed\")\ncli_code(\"result \u003c- compute()\")\ncli_end()\n```\n\n## Container Functions\n\nContainers create themed regions and manage element hierarchy. They auto-close when the function exits or can be closed explicitly with `cli_end()`.\n\n### General Containers\n\n**`cli_div()`** - Generic container for applying themes:\n\n```r\ncli_div(theme = list(\".emph\" = list(color = \"red\")))\ncli_text(\"This is {.emph emphasized} text\")\ncli_end()\n```\n\nWith classes:\n\n```r\ncli_div(class = \"my-section\", theme = list(\n \".my-section\" = list(\"margin-left\" = 2),\n \".my-section .code\" = list(color = \"blue\")\n))\ncli_text(\"Code: {.code mean(x)}\")\ncli_end()\n```\n\n**`cli_par()`** - Paragraph container:\n\n```r\ncli_par()\ncli_text(\"First line\")\ncli_text(\"Second line\")\ncli_end()\n```\n\n### List Containers\n\n**`cli_ul()` / `cli_ol()` / `cli_dl()`** - List containers:\n\n```r\n# Unordered list\ncli_ul()\ncli_li(\"First item\")\ncli_li(\"Second item\")\ncli_end()\n\n# Ordered list\ncli_ol()\ncli_li(\"Step one\")\ncli_li(\"Step two\")\ncli_end()\n\n# Definition list\ncli_dl()\ncli_li(c(term = \"Definition of term\"))\ncli_end()\n```\n\n### Auto-closing Behavior\n\nContainers automatically close when the calling function exits:\n\n```r\nmy_function \u003c- function() {\n cli_div(theme = list(\".alert\" = list(color = \"red\")))\n cli_alert(\"Alert message\")\n # No need to call cli_end() - auto-closes here\n}\n```\n\nExplicit closing with `cli_end()`:\n\n```r\nid \u003c- cli_div(theme = my_theme)\ncli_text(\"Themed content\")\ncli_end(id) # Close specific container\n```\n\n### Theme Scoping and Inheritance\n\nThemes inherit from parent containers and can be overridden:\n\n```r\n# Outer theme\ncli_div(theme = list(\".code\" = list(color = \"blue\")))\n\n# Inner theme overrides\ncli_div(theme = list(\".code\" = list(color = \"red\")))\ncli_text(\"Code is {.code red} here\")\ncli_end()\n\ncli_text(\"Code is {.code blue} here\")\ncli_end()\n```\n\n## Selector Types\n\n### Simple Selectors\n\nMatch elements by class:\n\n```r\nlist(\n \".code\" = list(color = \"blue\"), # Matches {.code ...}\n \".file\" = list(color = \"magenta\"), # Matches {.file ...}\n \".pkg\" = list(color = \"cyan\") # Matches {.pkg ...}\n)\n```\n\n### Element Type Selectors\n\nMatch by element type:\n\n```r\nlist(\n \"h1\" = list(color = \"blue\", \"font-weight\" = \"bold\"),\n \"ul\" = list(\"margin-left\" = 2),\n \"li\" = list(before = \"* \")\n)\n```\n\n### Descendant Selectors\n\nMatch elements within other elements:\n\n```r\nlist(\n \".my-section .code\" = list(color = \"blue\"),\n \".alert .emph\" = list(color = \"red\")\n)\n```\n\n### Multiple Selectors\n\nApply same styles to multiple selectors:\n\n```r\nlist(\n \".code, .fun, .fn\" = list(color = \"blue\")\n)\n```\n\n### Pseudo-selectors\n\nMatch specific states or positions:\n\n```r\nlist(\n \"li:before\" = list(content = \"-> \"),\n \"ul li:first-child\" = list(\"margin-top\" = 0)\n)\n```\n\n## Theme Properties\n\n### Color Properties\n\n**`color`** - Text color:\n\n```r\nlist(\".alert\" = list(color = \"red\"))\n```\n\n**`background-color`** - Background color:\n\n```r\nlist(\".code\" = list(\"background-color\" = \"grey90\"))\n```\n\nColor formats:\n- Named colors: `\"red\"`, `\"blue\"`, `\"green\"`\n- ANSI colors: `\"ansi_red\"`, `\"ansi_bright_blue\"`\n- RGB hex: `\"#FF5733\"`\n- RGB function: `rgb(255, 87, 51)`\n\n### Text Formatting\n\n**`font-weight`** - Text weight:\n\n```r\nlist(\".strong\" = list(\"font-weight\" = \"bold\"))\n```\n\n**`font-style`** - Text style:\n\n```r\nlist(\".emph\" = list(\"font-style\" = \"italic\"))\n```\n\n**`text-decoration`** - Text decoration:\n\n```r\nlist(\".url\" = list(\"text-decoration\" = \"underline\"))\n```\n\n### Spacing Properties\n\n**`margin-left`** - Left margin (in characters):\n\n```r\nlist(\".par\" = list(\"margin-left\" = 2))\n```\n\n**`margin-right`** - Right margin:\n\n```r\nlist(\".par\" = list(\"margin-right\" = 2))\n```\n\n**`margin-top`** - Top margin (in lines):\n\n```r\nlist(\"h1\" = list(\"margin-top\" = 1, \"margin-bottom\" = 1))\n```\n\n**`padding-left`** - Left padding:\n\n```r\nlist(\".alert\" = list(\"padding-left\" = 2))\n```\n\n### Content Properties\n\n**`before`** - Content before element:\n\n```r\nlist(\n \".alert-success:before\" = list(content = \"[OK] \"),\n \"ul li:before\" = list(content = \"* \")\n)\n```\n\n**`after`** - Content after element:\n\n```r\nlist(\".code:after\" = list(content = \" }\"))\n```\n\n### List Properties\n\n**`list-style-type`** - List marker style:\n\n```r\nlist(\n \"ul\" = list(\"list-style-type\" = \"bullet\"),\n \"ol\" = list(\"list-style-type\" = \"decimal\")\n)\n```\n\nValues: `\"bullet\"`, `\"circle\"`, `\"square\"`, `\"decimal\"`, `\"lower-alpha\"`, `\"upper-alpha\"`\n\n**`start`** - Ordered list start number:\n\n```r\nlist(\"ol\" = list(start = 5))\n```\n\n### Line Properties\n\n**`line-type`** - Line drawing style:\n\n```r\nlist(\".rule\" = list(\"line-type\" = \"double\"))\n```\n\nValues: `\"single\"`, `\"double\"`, `\"bar1\"` through `\"bar8\"`\n\n### Format Control\n\n**`fmt`** - Custom format function:\n\n```r\nlist(\n \".timestamp\" = list(\n fmt = function(x) format(Sys.time(), \"%Y-%m-%d %H:%M:%S\")\n )\n)\n```\n\n**`transform`** - Transform function:\n\n```r\nlist(\n \".upper\" = list(transform = toupper)\n)\n```\n\n## Built-in Themes\n\n### Default Theme\n\nThe standard cli theme with semantic colors and spacing:\n\n```r\n# View built-in theme structure\nstr(builtin_theme(), max.level = 2)\n```\n\nKey elements:\n- Blue for code, functions, arguments\n- Magenta for files and paths\n- Cyan for packages\n- Green for success\n- Red for errors\n- Yellow for warnings\n\n### Simple Theme\n\nA minimal theme without colors:\n\n```r\noptions(cli.theme = simple_theme())\n```\n\nUseful for:\n- Terminals without color support\n- Logging to files\n- Screen readers\n- Testing\n\n### Dark Theme\n\nOptimized for dark terminal backgrounds (included in default theme with automatic detection).\n\n## Custom Themes\n\n### Creating a Custom Theme\n\nBuild themes incrementally:\n\n```r\nmy_theme \u003c- list(\n # Headers\n \"h1\" = list(\n color = \"blue\",\n \"font-weight\" = \"bold\",\n \"margin-top\" = 1,\n \"margin-bottom\" = 1,\n before = \"== \",\n after = \" ==\"\n ),\n\n # Code elements\n \".code\" = list(\n color = \"cyan\",\n \"background-color\" = \"grey10\"\n ),\n\n \".fn\" = list(\n color = \"blue\",\n after = \"()\"\n ),\n\n # Alerts\n \".alert-success\" = list(\n before = \"[OK] \",\n color = \"green\"\n ),\n\n \".alert-danger\" = list(\n before = \"[ERROR] \",\n color = \"red\",\n \"font-weight\" = \"bold\"\n ),\n\n # Lists\n \"ul li\" = list(\n before = \"• \"\n ),\n\n \"ol li\" = list(\n \"list-style-type\" = \"decimal\"\n )\n)\n```\n\n### Extending Built-in Themes\n\nMerge your theme with the built-in theme:\n\n```r\nmy_theme \u003c- utils::modifyList(\n builtin_theme(),\n list(\n \".code\" = list(color = \"magenta\"),\n \".custom\" = list(color = \"cyan\")\n )\n)\n\ncli_div(theme = my_theme)\n```\n\n### Theme Functions\n\nCreate reusable theme functions:\n\n```r\ncreate_brand_theme \u003c- function(primary_color = \"blue\") {\n list(\n \"h1\" = list(color = primary_color, \"font-weight\" = \"bold\"),\n \"h2\" = list(color = primary_color),\n \".code\" = list(color = primary_color),\n \".alert-success\" = list(color = \"green\"),\n \".alert-danger\" = list(color = \"red\")\n )\n}\n\ncli_div(theme = create_brand_theme(\"purple\"))\n```\n\n## App and Package Themes\n\n### Setting Package Theme\n\nDefine a package-level theme in your `.onLoad()`:\n\n```r\n.onLoad \u003c- function(libname, pkgname) {\n my_theme \u003c- list(\n \".code\" = list(color = \"blue\"),\n \".pkg\" = list(color = \"cyan\")\n )\n\n options(cli.theme = my_theme)\n}\n```\n\n### User Configuration\n\nUsers can override package themes via options:\n\n```r\n# In .Rprofile\noptions(cli.theme = list(\n \".code\" = list(color = \"magenta\")\n))\n```\n\n### Conditional Themes\n\nApply themes based on environment:\n\n```r\n.onLoad \u003c- function(libname, pkgname) {\n theme \u003c- if (cli::num_ansi_colors() >= 256) {\n rich_theme() # Full color theme\n } else {\n simple_theme() # Basic theme\n }\n\n options(cli.theme = theme)\n}\n```\n\n### Theme Precedence\n\nThemes are applied in this order (highest to lowest):\n1. Inline theme in `cli_div(theme = ...)`\n2. User's `cli.theme` option\n3. Package's default theme\n4. Built-in theme\n\n## Color Palettes\n\n### Configuring Palettes\n\nSet the ANSI color palette with the `cli.palette` option:\n\n```r\noptions(cli.palette = \"vscode\")\n```\n\n### Built-in Palettes\n\n**`dichro`** - Dichromat-friendly palette:\n```r\noptions(cli.palette = \"dichro\")\n```\n\n**`vscode`** - VS Code color scheme:\n```r\noptions(cli.palette = \"vscode\")\n```\n\n**`iterm`** - iTerm2 default colors:\n```r\noptions(cli.palette = \"iterm\")\n```\n\n### Custom 16-Color Palettes\n\nDefine custom ANSI colors:\n\n```r\nmy_palette \u003c- c(\n # Normal colors (0-7)\n \"#000000\", # black\n \"#CD0000\", # red\n \"#00CD00\", # green\n \"#CDCD00\", # yellow\n \"#0000EE\", # blue\n \"#CD00CD\", # magenta\n \"#00CDCD\", # cyan\n \"#E5E5E5\", # white\n\n # Bright colors (8-15)\n \"#7F7F7F\", # bright black (grey)\n \"#FF0000\", # bright red\n \"#00FF00\", # bright green\n \"#FFFF00\", # bright yellow\n \"#5C5CFF\", # bright blue\n \"#FF00FF\", # bright magenta\n \"#00FFFF\", # bright cyan\n \"#FFFFFF\" # bright white\n)\n\noptions(cli.palette = my_palette)\n```\n\n### Truecolor Support\n\nCheck for truecolor support:\n\n```r\ncli::num_ansi_colors()\n# Returns:\n# 1 - no color\n# 8 - 8 colors\n# 256 - 256 colors\n# 16777216 - truecolor (24-bit)\n```\n\nUse truecolor when available:\n\n```r\nif (cli::num_ansi_colors() >= 16777216) {\n # Use RGB hex colors\n list(\".code\" = list(color = \"#6A9FB5\"))\n} else {\n # Fall back to named colors\n list(\".code\" = list(color = \"blue\"))\n}\n```\n\n### Color Detection\n\nCLI automatically detects color support from:\n- `NO_COLOR` environment variable (disables color)\n- `TERM` environment variable\n- System capabilities\n- RStudio version\n\nForce color support:\n\n```r\noptions(cli.num_colors = 256) # Force 256 colors\n```\n\nDisable colors:\n\n```r\noptions(cli.num_colors = 1)\n# Or set environment variable\nSys.setenv(NO_COLOR = \"1\")\n```\n\n## Accessibility\n\n### Color Contrast\n\nEnsure sufficient contrast for readability:\n\n```r\n# Good contrast\nlist(\n \".code\" = list(color = \"blue\"), # Dark on light\n \".emph\" = list(color = \"ansi_red\") # High contrast\n)\n\n# Poor contrast (avoid)\nlist(\n \".code\" = list(color = \"grey80\"), # Low contrast on white\n \".emph\" = list(color = \"#EEEEEE\") # Nearly invisible on white\n)\n```\n\n### Unicode Fallbacks\n\nProvide ASCII alternatives for Unicode symbols:\n\n```r\nbullet \u003c- if (cli::is_utf8_output()) \"\\u2022\" else \"*\"\n\nlist(\n \"ul li:before\" = list(content = bullet)\n)\n```\n\nBuilt-in Unicode detection:\n\n```r\ncli::is_utf8_output() # TRUE if UTF-8 is supported\n```\n\n### Color-blind Friendly Themes\n\nUse the dichromat palette or ensure patterns work without color:\n\n```r\ncolor_blind_theme \u003c- list(\n \".alert-success\" = list(\n before = \"[OK] \",\n color = \"green\"\n ),\n \".alert-danger\" = list(\n before = \"[ERROR] \",\n color = \"red\",\n \"font-weight\" = \"bold\"\n )\n)\n```\n\nBenefits:\n- Symbols provide meaning without color\n- Bold text adds emphasis\n- Works for colorblind users\n\n### Screen Reader Compatibility\n\nKeep semantic meaning in text, not just styling:\n\n```r\n# Good: Meaning is in text\ncli_alert_success(\"File saved successfully\")\n\n# Poor: Meaning only in style\ncli_text(\"{.green File saved}\")\n```\n\n## Debugging Themes\n\n### Using cli_debug_doc()\n\nVisualize document structure and applied themes:\n\n```r\n# Enable debug mode\nwithr::local_options(cli.debug = TRUE)\n\ncli_div(class = \"my-section\")\ncli_h1(\"Header\")\ncli_text(\"Text with {.code code}\")\ncli_end()\n```\n\nDebug output shows:\n- Element hierarchy\n- Applied selectors\n- Computed properties\n- Theme inheritance\n\n### Inspecting Theme Application\n\nCheck which theme properties are applied:\n\n```r\n# Create a test container\ncli_div(theme = my_theme, class = \"test\")\n\n# Debug output will show:\n# - Matched selectors\n# - Applied properties\n# - Inherited values\n\ncli_text(\"Test content\")\ncli_end()\n```\n\n### Theme Testing Strategy\n\nTest themes across different environments:\n\n```r\ntest_theme \u003c- function(theme) {\n old_colors \u003c- options(cli.num_colors = 256)\n on.exit(options(old_colors))\n\n cli_div(theme = theme)\n cli_h1(\"Header\")\n cli_alert_success(\"Success message\")\n cli_text(\"Code: {.code mean(x)}\")\n cli_end()\n}\n\n# Test with different color depths\ntest_colors \u003c- c(1, 8, 256, 16777216)\nfor (n in test_colors) {\n options(cli.num_colors = n)\n test_theme(my_theme)\n}\n```\n\n### Common Theme Issues\n\n**Colors not appearing:**\n- Check `cli::num_ansi_colors()` output\n- Verify terminal supports colors\n- Check for `NO_COLOR` environment variable\n\n**Spacing incorrect:**\n- Use `cli.debug = TRUE` to see computed margins\n- Check for inherited spacing properties\n- Verify units (characters vs. lines)\n\n**Selectors not matching:**\n- Use `cli.debug = TRUE` to see selector matching\n- Check selector syntax (spaces for descendants)\n- Verify class names match inline markup\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13702,"content_sha256":"27469ad2c432bae1ce4c18438500a081d4447ce595c5797aed41e61d42479f90"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"CLI for R Packages","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use What","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Display error with context and formatting use: ","type":"text"},{"text":"cli_abort()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with inline markup and bullet lists","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Show warning with formatting use: ","type":"text"},{"text":"cli_warn()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with inline markup","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Display informative message use: ","type":"text"},{"text":"cli_inform()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with inline markup","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Show progress for counted operations use: ","type":"text"},{"text":"cli_progress_bar()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with total count","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Show simple progress steps use: ","type":"text"},{"text":"cli_progress_step()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with status messages","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Format code or function names use: ","type":"text"},{"text":"{.code ...}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"{.fn package::function}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Format file paths use: ","type":"text"},{"text":"{.file path/to/file}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Format package names use: ","type":"text"},{"text":"{.pkg packagename}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Format variable names use: ","type":"text"},{"text":"{.var variable_name}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Format values use: ","type":"text"},{"text":"{.val value}","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Handle singular/plural text use: ","type":"text"},{"text":"{?s}","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"{?y/ies}","type":"text","marks":[{"type":"code_inline"}]},{"text":" with pluralization","type":"text"}]},{"type":"paragraph","content":[{"text":"task: Create headers use: ","type":"text"},{"text":"cli_h1()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_h2()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_h3()","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Create alerts use: ","type":"text"},{"text":"cli_alert_success()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_alert_danger()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_alert_warning()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_alert_info()","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"task: Create lists use: ","type":"text"},{"text":"cli_ul()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_ol()","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"cli_dl()","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"cli_li()","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Inline Markup Essentials","type":"text"}]},{"type":"paragraph","content":[{"text":"Use inline markup with ","type":"text"},{"text":"{.class content}","type":"text","marks":[{"type":"code_inline"}]},{"text":" syntax to format text:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Basic formatting\ncli_text(\"Function {.fn mean} calculates averages\")\ncli_text(\"Install package {.pkg dplyr}\")\ncli_text(\"See file {.file ~/.Rprofile}\")\ncli_text(\"{.var x} must be numeric, not {.obj_type_of {x}}\")\ncli_text(\"Got value {.val {x}}\")\n\n# Code formatting\ncli_text(\"Use {.code sum(x, na.rm = TRUE)}\")\n\n# Paths and arguments\ncli_text(\"Reading from {.path /data/file.csv}\")\ncli_text(\"Set {.arg na.rm} to TRUE\")\n\n# Types and classes\ncli_text(\"Object is {.cls data.frame}\")\n\n# Emphasis\ncli_text(\"This is {.emph important}\")\ncli_text(\"This is {.strong critical}\")\n\n# Fields\ncli_text(\"The {.field name} field is required\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Vector Collapsing","type":"text"}]},{"type":"paragraph","content":[{"text":"Vectors are automatically collapsed with commas and \"and\":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"pkgs \u003c- c(\"dplyr\", \"tidyr\", \"ggplot2\")\ncli_text(\"Installing packages: {.pkg {pkgs}}\")\n#> Installing packages: dplyr, tidyr, and ggplot2\n\nfiles \u003c- c(\"data.csv\", \"script.R\")\ncli_text(\"Found {length(files)} file{?s}: {.file {files}}\")\n#> Found 2 files: data.csv and script.R","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Escaping Braces","type":"text"}]},{"type":"paragraph","content":[{"text":"Use double braces ","type":"text"},{"text":"{{","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"}}","type":"text","marks":[{"type":"code_inline"}]},{"text":" to escape literal braces:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"cli_text(\"Use {{variable}} syntax in glue\")\n#> Use {variable} syntax in glue","type":"text"}]},{"type":"paragraph","content":[{"text":"For complete markup reference","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"references/inline-markup.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/inline-markup.md","title":null}}]},{"text":" for all 50+ inline classes, edge cases, nesting rules, and advanced patterns.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Pluralization Basics","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"{?}","type":"text","marks":[{"type":"code_inline"}]},{"text":" for pluralization with three patterns:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Single Alternative","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"nfile \u003c- 1\ncli_text(\"Found {nfile} file{?s}\")\n#> Found 1 file\n\nnfile \u003c- 3\ncli_text(\"Found {nfile} file{?s}\")\n#> Found 3 files","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Two Alternatives","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"ndir \u003c- 1\ncli_text(\"Found {ndir} director{?y/ies}\")\n#> Found 1 directory\n\nndir \u003c- 5\ncli_text(\"Found {ndir} director{?y/ies}\")\n#> Found 5 directories","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Three Alternatives (zero/one/many)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"nfile \u003c- 0\ncli_text(\"Found {nfile} file{?s}: {?no/the/the} file{?s}\")\n#> Found 0 files: no files\n\nnfile \u003c- 1\ncli_text(\"Found {nfile} file{?s}: {?no/the/the} file{?s}\")\n#> Found 1 file: the file\n\nnfile \u003c- 3\ncli_text(\"Found {nfile} file{?s}: {?no/the/the} file{?s}\")\n#> Found 3 files: the files","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Helpers: qty() and no()","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"no()","type":"text","marks":[{"type":"code_inline"}]},{"text":" to display \"no\" instead of zero:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"nfile \u003c- 0\ncli_text(\"Found {no(nfile)} file{?s}\")\n#> Found no files","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"qty()","type":"text","marks":[{"type":"code_inline"}]},{"text":" to set quantity explicitly:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"nupd \u003c- 3\nntotal \u003c- 10\ncli_text(\"{nupd}/{ntotal} {qty(nupd)} file{?s} {?needs/need} updates\")\n#> 3/10 files need updates","type":"text"}]},{"type":"paragraph","content":[{"text":"For advanced pluralization","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"references/inline-markup.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/inline-markup.md","title":null}}]},{"text":" for edge cases and complex patterns.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"CLI Conditions: Core Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"Use cli conditions instead of base R for better formatting:","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cli_abort() - Formatted Errors","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Before (base R)\nstop(\"File not found: \", path)\n\n# After (cli)\ncli_abort(\"File {.file {path}} not found\")\n\n# With bullets for context\ncheck_file \u003c- function(path) {\n if (!file.exists(path)) {\n cli_abort(c(\n \"File not found\",\n \"x\" = \"Cannot read {.file {path}}\",\n \"i\" = \"Check that the file exists\"\n ))\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cli_warn() - Formatted Warnings","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Before (base R)\nwarning(\"Column \", col, \" has missing values\")\n\n# After (cli)\ncli_warn(\"Column {.field {col}} has missing values\")\n\n# With context\ncli_warn(c(\n \"Data quality issues detected\",\n \"!\" = \"Column {.field {col}} has {n_missing} missing value{?s}\",\n \"i\" = \"Consider using {.fn tidyr::drop_na}\"\n))","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"cli_inform() - Formatted Messages","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Before (base R)\nmessage(\"Processing \", n, \" files\")\n\n# After (cli)\ncli_inform(\"Processing {n} file{?s}\")\n\n# With structure\ncli_inform(c(\n \"v\" = \"Successfully loaded {.pkg dplyr}\",\n \"i\" = \"Version {packageVersion('dplyr')}\"\n))","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bullet Types","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"x\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Error/problem (red X)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"!\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Warning (yellow !)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"i\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Information (blue i)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"v\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Success (green checkmark)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"*\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Bullet point","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\">\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Arrow/pointer","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"For advanced error design","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"references/conditions.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/conditions.md","title":null}}]},{"text":" for error design principles, rlang integration, testing strategies, and real-world patterns.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Basic Progress Indicators","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Simple Progress Steps","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"process_data \u003c- function() {\n cli_progress_step(\"Loading data\")\n data \u003c- load_data()\n\n cli_progress_step(\"Cleaning data\")\n clean \u003c- clean_data(data)\n\n cli_progress_step(\"Analyzing data\")\n analyze(clean)\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Progress Bar","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"process_files \u003c- function(files) {\n cli_progress_bar(\"Processing files\", total = length(files))\n\n for (file in files) {\n process_file(file)\n cli_progress_update()\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Auto-Cleanup","type":"text"}]},{"type":"paragraph","content":[{"text":"Progress bars auto-close when the function exits:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"process \u003c- function() {\n cli_progress_bar(\"Working\", total = 100)\n for (i in 1:100) {\n Sys.sleep(0.01)\n cli_progress_update()\n }\n # No need to call cli_progress_done() - auto-closes\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"For advanced progress","type":"text","marks":[{"type":"strong"}]},{"text":": See ","type":"text"},{"text":"references/progress.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/progress.md","title":null}}]},{"text":" for nested progress, custom formats, parallel processing, all progress variables, and Shiny integration.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Semantic CLI Elements","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Headers","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"cli_h1(\"Main Section\")\ncli_h2(\"Subsection\")\ncli_h3(\"Detail\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Alerts","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"cli_alert_success(\"Operation completed successfully\")\ncli_alert_danger(\"Critical error occurred\")\ncli_alert_warning(\"Potential issue detected\")\ncli_alert_info(\"Additional information available\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Text and Code","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Regular text with markup\ncli_text(\"This is formatted text with {.emph emphasis}\")\n\n# Code blocks\ncli_code(c(\n \"library(dplyr)\",\n \"mtcars %>% filter(mpg > 20)\"\n))\n\n# Verbatim text (no formatting)\ncli_verbatim(\"This is displayed exactly as-is: {not interpolated}\")","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Lists","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Unordered list\ncli_ul()\ncli_li(\"First item\")\ncli_li(\"Second item\")\ncli_end()\n\n# Ordered list\ncli_ol()\ncli_li(\"First step\")\ncli_li(\"Second step\")\ncli_end()\n\n# Definition list\ncli_dl()\ncli_li(c(name = \"The name field\"))\ncli_li(c(email = \"The email address\"))\ncli_end()","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Workflows","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Base R to CLI Migration","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"# Before: Base R error handling\nvalidate_input \u003c- function(x, y) {\n if (!is.numeric(x)) {\n stop(\"x must be numeric\")\n }\n if (length(y) == 0) {\n stop(\"y cannot be empty\")\n }\n if (length(x) != length(y)) {\n stop(\"x and y must have the same length\")\n }\n}\n\n# After: CLI error handling\nvalidate_input \u003c- function(x, y) {\n if (!is.numeric(x)) {\n cli_abort(c(\n \"{.arg x} must be numeric\",\n \"x\" = \"You supplied a {.cls {class(x)}} vector\",\n \"i\" = \"Use {.fn as.numeric} to convert\"\n ))\n }\n\n if (length(y) == 0) {\n cli_abort(c(\n \"{.arg y} cannot be empty\",\n \"i\" = \"Provide at least one element\"\n ))\n }\n\n if (length(x) != length(y)) {\n cli_abort(c(\n \"{.arg x} and {.arg y} must have the same length\",\n \"x\" = \"{.arg x} has length {length(x)}\",\n \"x\" = \"{.arg y} has length {length(y)}\"\n ))\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Error Message with Rich Context","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"check_required_columns \u003c- function(data, required_cols) {\n actual_cols \u003c- names(data)\n missing_cols \u003c- setdiff(required_cols, actual_cols)\n\n if (length(missing_cols) > 0) {\n cli_abort(c(\n \"Required column{?s} missing from data\",\n \"x\" = \"Missing {length(missing_cols)} column{?s}: {.field {missing_cols}}\",\n \"i\" = \"Data has {length(actual_cols)} column{?s}: {.field {actual_cols}}\",\n \"i\" = \"Add the missing column{?s} or check for typos\"\n ))\n }\n\n invisible(data)\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Function with Progress Bar","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"r"},"content":[{"text":"process_files \u003c- function(files, verbose = TRUE) {\n n \u003c- length(files)\n\n if (verbose) {\n cli_progress_bar(\n format = \"Processing {cli::pb_bar} {cli::pb_current}/{cli::pb_total} [{cli::pb_eta}]\",\n total = n\n )\n }\n\n results \u003c- vector(\"list\", n)\n\n for (i in seq_along(files)) {\n results[[i]] \u003c- process_file(files[[i]])\n\n if (verbose) {\n cli_progress_update()\n }\n }\n\n results\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources & Advanced Topics","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Reference Files","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/inline-markup.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/inline-markup.md","title":null}},{"type":"strong"}]},{"text":" - Complete catalog of inline classes organized by category, advanced patterns, nesting rules, and real-world examples","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/conditions.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/conditions.md","title":null}},{"type":"strong"}]},{"text":" - Advanced error design patterns, rlang integration, testing with testthat snapshots, migration guide, and anti-patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/progress.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/progress.md","title":null}},{"type":"strong"}]},{"text":" - Nested progress bars, custom formats, all progress variables, parallel processing, Shiny integration, and debugging","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/themes.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/themes.md","title":null}},{"type":"strong"}]},{"text":" - Complete theming system with CSS-like selectors, container functions, color palettes, custom themes, and accessibility","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/ansi-operations.md","type":"text","marks":[{"type":"link","attrs":{"href":"references/ansi-operations.md","title":null}},{"type":"strong"}]},{"text":" - ANSI string operations (align, columns, nchar, etc.), hyperlinks, color detection, testing CLI output, and troubleshooting","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"External Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cli package documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://cli.r-lib.org","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cli GitHub repository","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/r-lib/cli","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building a semantic CLI (article)","type":"text","marks":[{"type":"link","attrs":{"href":"https://cli.r-lib.org/articles/semantic-cli.html","title":null}}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Related Packages","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"rlang","type":"text","marks":[{"type":"strong"}]},{"text":" - Condition handling and error objects integrate with cli","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"glue","type":"text","marks":[{"type":"strong"}]},{"text":" - String interpolation powers cli's ","type":"text"},{"text":"{}","type":"text","marks":[{"type":"code_inline"}]},{"text":" syntax","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"testthat","type":"text","marks":[{"type":"strong"}]},{"text":" - Snapshot testing for cli output","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"cli","author":"@skillopedia","source":{"stars":380,"repo_name":"skills","origin_url":"https://github.com/posit-dev/skills/blob/HEAD/r-lib/cli/SKILL.md","repo_owner":"posit-dev","body_sha256":"66e88f050e3e0396fb5c537e00119e3e8e8e3f31e2db55207277cc38eda98613","cluster_key":"175423581e28676cfe2b5671f31dda6f4716442f5093b293734a10d4594f8a3a","clean_bundle":{"format":"clean-skill-bundle-v1","source":"posit-dev/skills/r-lib/cli/SKILL.md","attachments":[{"id":"146a2352-c6f9-5369-a21f-54ecb3584674","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/146a2352-c6f9-5369-a21f-54ecb3584674/attachment.md","path":"references/ansi-operations.md","size":19761,"sha256":"3b86a218216d2c987d4efbf77c8a1231a9c8827e163b69e604809cf399f40e1a","contentType":"text/markdown; charset=utf-8"},{"id":"85138087-edcb-5ae3-bef9-e63c784363bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/85138087-edcb-5ae3-bef9-e63c784363bd/attachment.md","path":"references/conditions.md","size":18876,"sha256":"fbf1013acbf13ce227ec1dd86fd0714bf96ca4fdd8115a3f234bb88f7751f169","contentType":"text/markdown; charset=utf-8"},{"id":"6859d71a-d9e2-53a1-be0b-7d90f4e5e6ae","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6859d71a-d9e2-53a1-be0b-7d90f4e5e6ae/attachment.md","path":"references/inline-markup.md","size":19774,"sha256":"1ed5cb03a034ffa5776c781ae0215f734e1bd8556fe3551470a5dd1e884216ce","contentType":"text/markdown; charset=utf-8"},{"id":"a6a05ac0-4bb3-5f43-bbcf-35435c409b7f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a6a05ac0-4bb3-5f43-bbcf-35435c409b7f/attachment.md","path":"references/progress.md","size":20545,"sha256":"d79980825ff26fa36cb694e12874720945d3d6c28cbc04805db6ebb3fdf996f9","contentType":"text/markdown; charset=utf-8"},{"id":"49a509e1-9528-5666-9526-a3619585960a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/49a509e1-9528-5666-9526-a3619585960a/attachment.md","path":"references/themes.md","size":13702,"sha256":"27469ad2c432bae1ce4c18438500a081d4447ce595c5797aed41e61d42479f90","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"f9ac0d0846b9800431a6f47757e29b89d2760ac4ead35dbde41e5d5bd5b10b24","attachment_count":5,"text_attachments":5,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"r-lib/cli/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"web-development","metadata":{"author":"Garrick Aden-Buie (@gadenbuie)","version":"1.0"},"import_tag":"clean-skills-v1","description":"Comprehensive R package for command-line interface styling, semantic messaging, and user communication. Use this skill when working with R code that needs to: (1) Format console output with inline markup and colors, (2) Display errors, warnings, or messages with cli_abort/cli_warn/cli_inform, (3) Show progress indicators for long-running operations, (4) Create semantic CLI elements (headers, lists, alerts, code blocks), (5) Apply themes and customize output styling, (6) Handle pluralization in user-facing text, (7) Work with ANSI strings, hyperlinks, or custom containers. Also use when migrating from base R message/warning/stop, debugging cli code, or improving existing cli usage.\n"}},"renderedAt":1782979271380}

CLI for R Packages When to Use What task: Display error with context and formatting use: with inline markup and bullet lists task: Show warning with formatting use: with inline markup task: Display informative message use: with inline markup task: Show progress for counted operations use: with total count task: Show simple progress steps use: with status messages task: Format code or function names use: or task: Format file paths use: task: Format package names use: task: Format variable names use: task: Format values use: task: Handle singular/plural text use: or with pluralization task: Cre…