A minimal Quarto document is barely more than YAML + one code chunk.
hello.qmd
---title: "My first Quarto doc"format: html---```{r}#| label: fig-mpg#| fig-cap: "Distribution of mpg"hist(mtcars$mpg)```
Render is one click. #| label: names the figure for a cross-reference.
2.5 Differences that matter
The same skeleton, but the moving parts have names and defaults:
Table 1: R Markdown vs Quarto at a glance
Aspect
R Markdown (.Rmd)
Quarto (.qmd)
Languages
R, via knitr
Same R, plus Python, Julia, OJS
Output
output: in YAML
format: in YAML
Chunk options
{r, echo=FALSE} header
#| echo: false lines (old form still works)
Projects
_site.yml for websites
_quarto.yml, first-class for any project
Slides
xaringan, slidy, ioslides
Native format: revealjs
Note
The useful habit: treat Quarto as RMarkdown that grew up. Nothing you know is wasted; most of it just got a new name.
2.6 Quarto projects
A folder with a _quarto.yml becomes a project: one file configuring the whole folder. Four built-in types – website, book, manuscript, or default.
_quarto.yml
project:type: default # default | website | book | manuscriptoutput-dir: _outputexecute:freeze:true # cache results, rerun only when inputs changeformat:html:toc:truetheme: cosmo
quarto preview rebuilds + live-reloads project as you edit; quarto render builds final output; quarto publish deploys it. See the project reference.
2.7 One brand, everywhere
A _brand.yml at the project root carries colors, fonts and logo in one place; one change re-skins every report, deck and app.
_brand.yml
color:palette:teal:"#2E7D6B"amber:"#E8A33D"primary: tealsecondary: ambertypography:base: Interfonts:-family: Inter
Apply it via the brand key, or let Quarto/Shiny auto-discover _brand.yml. See the brand reference.
2.8 Render targets
Quarto renders many formats from the same source. The key commands:
Table 2: Same source, many targets
Command
Output
Render button
The format in YAML format:
quarto render f.qmd --to html
Force a specific format
quarto preview
Live-reload preview
quarto render --to revealjs
A slide deck from a folder
Quick check. Open example-qmd/ and render the hello.qmd example to HTML. Confirm the output file appears next to the source.
2.9 From R Markdown to Quarto in practice
Converting existing analysis is mechanical. Same code, few renamed keys:
analysis.Rmd
---output: html_documentruntime: shiny # Rmd: live Shiny in a document---```{r, echo = FALSE}hist(mtcars$mpg)```
analysis.qmd
---format: htmlserver: shiny # Qmd: live Shiny in a document---```{r}#| echo: falsehist(mtcars$mpg)```
output: becomes nested format:.
{r, opt=} headers become #| opt: lines.
Live Shiny: Rmd’s runtime: shiny is renamed to server: shiny in Qmd.
2.10 Cross-references and citations
Cite anything by label, not by a shifting number. As the two figures auto-number below.
report.qmd (editor)
---bibliography: references.bib---See @fig-mpg, @fig-rank and @wickham2016.```{r}#| label: fig-mpg#| fig-cap: "Distribution of mpg"hist(mtcars$mpg)``````{r}#| label: fig-rank#| fig-cap: "Ranks of mpg values"plot(rank(mtcars$mpg))```
2.11 Callouts
Quarto can emphasise a note, a tip, a warning with a callout (fenced div with a callout-* class). Five built-ins: note, tip, important, warning, caution.
::: {.callout-tip title="Tip"}Your note, tip, warning or caution goes here.:::
Tip
Your note, tip, warning or caution goes here.
::: {.callout-caution title="Caution"}Your note, tip, warning or caution goes here.:::
Caution
Your note, tip, warning or caution goes here.
::: {.callout-warning title="Warning"}Your note, tip, warning or caution goes here.:::
Warning
Your note, tip, warning or caution goes here.
::: {.callout-important title="Important"}Your note, tip, warning or caution goes here.:::
Important
Your note, tip, warning or caution goes here.
2.12 Why Quarto for our purpose
For this workshop, Quarto matters for three reasons that build toward the final goal:
Structured reports. Text, code and figures interleaved in one reproducible document.
Embedded interactivity. Either live Shiny (server: shiny in Qmd) on a server, or Shinylive blocks (or Observable JS) that run in the browser.
Static output. A rendered Quarto HTML page is plain files, ready for any static host.
2.13 Hands-on bandwidth
Now open labs/lab-01-quarto.qmd and work through it. It covers:
Writing your first .qmd and choosing an output format.
Structure, headings, lists, code chunks and inline code.
A comparison table of .Rmd vs .qmd chunk options.
Adding a caption and cross-referencing a figure.
2.14 Summary so far
Quarto = RMarkdown, generalised: Python, Julia and Observable join R.
Projects (_quarto.yml) centralise config; one source, many formats.
Rendered output is plain files → a perfect fit for static hosting later.
Why Quarto V2
Instant start. A single self-contained binary; launches and renders faster.
Native PDF, no LaTeX. Embedded Typst engine: polished PDFs from plain text.
Live authoring. Hot reload and fast incremental renders while you edit.
Collaborative. Live-preview session links for real-time team co-editing.
Backwards compatible. Quarto 1 projects and extensions keep working.
3 Shiny
Up next: Shiny.
Quarto
Shiny
The reactive model: inputs, outputs and renderers, and how Shiny embeds in a Quarto document.
Shinylive
Wrap-up
Quarto · doneShiny · nowShinylive · nextWrap-up · soon
3.1 What Shiny is
Shiny is a framework for interactive web applications: you write an app in R, and Shiny serves it as a web page that reacts to the reader’s actions. It is not a purely R idea – later we note the Python flavour – but we go through R throughout today.
UI. The layout: sliders, buttons, tables, plots, text.
Server. The logic: what happens when the user changes an input.
Reactive wiring. What Shiny calls the “reactive graph”: inputs flow to outputs.
3.2 A minimal Shiny app
Every Shiny app has two pieces. The smallest possible app:
app.R
library(shiny)ui <-fluidPage(sliderInput(inputId ="n", label ="Number of points",min =10, max =200, value =50),textOutput(outputId ="msg"))server <-function(input, output, session) { output$msg <-renderText({paste("You chose", input$n, "points") })}shinyApp(ui = ui, server = server)
Run it with Run App (or shiny::runApp("app.R")).
3.3 The reactive model, visually
Shiny tracks dependencies so only what changed re-renders.
flowchart LR
I["input$n (slider)"] --> R["reactive expression<br/>computed once, cached"]
R --> M["output$msg<br/>renderText"]
R --> P["output$plot<br/>renderPlot"]
Note
Reactive flow:
input change invalidates expressions that read it, which in turn invalidate outputs that depend on them
Shiny recomputes only that minimal set.
Reactive expression: compute a value once + cache it \(\rightarrow\) dependent outputs reuse a single computation.
3.4 Inputs you will use today
Shiny ships a rich widget set. The four you will rely on for the labs:
Table 3: The four input widgets you will rely on
Widget
Function
Returns
Slider
sliderInput("id", "N", 0, 100, 30)
one number
Select
selectInput("id", "Var", choices)
one (or more) values
Checkbox
checkboxInput("id", "Label")
TRUE / FALSE
File upload
fileInput("id", "CSV")
a file handle, read with read.csv(input$id$datapath)
library(shiny); library(ggplot2); library(dplyr)ui <-fluidPage(titlePanel("Palmer penguins explorer"),fileInput("data", "Upload your data (CSV)", accept =".csv"),sliderInput("bins", "Number of bins", 5, 40, 20),plotOutput("hist"))server <-function(input, output, session) { peng <-reactive({ req(input$data); readr::read_csv(input$data$datapath) }) output$hist <-renderPlot({peng() |>ggplot(aes(body_mass_g)) +geom_histogram(bins = input$bins, fill ="#2E7D6B") })}shinyApp(ui, server)
reactive({}) reads the file once, then re-runs only when the upload changes. Run it at example-app/app.R.
3.7 Reactive details that trip people up
Reactives are lazy and cached: a reactive({}) recomputes only when its inputs change.
req() guards: req(input$x) halts until input$x is non-empty, avoiding errors on first render.
eventReactive(): for click-driven actions (button → go), when you do not want recomputation on every keystroke.
Side-effects live in observers or render*(): a reactive() only computes a value. To do something when an input changes – a showNotification(), refreshed updateSelectInput() choices, a revealed panel – use an observer (observe()) or an output.
3.8 Shiny inside a Quarto document
“Shiny in Quarto” covers two very different setups; keep them apart. The classic, server-backed route first:
In Quarto, server: shiny needs a server; our goal is the static route.
Shiny is not just R
A Python flavour of Shiny exists too, with the same ideas in another language.
So the question: what if we shipped the app itself to the reader’s browser instead of hosting it on an R process? That is Shinylive (WebAssembly, webR, and a web asset library), which is next up.
4 Shinylive
Up next: Shinylive.
Quarto
Shiny
Shinylive
WASM and webR: export a standalone app, or embed a report. Both run entirely in the browser, no server.
Wrap-up
Quarto · doneShiny · doneShinylive · nowWrap-up · next
4.1 Why host without a server?
Shiny traditionally needs a server program running around the clock, which takes time, expertise and money to keep up.
Shinylive is the alternative: the whole app becomes plain files that any static host can serve, so the browser does the computing.
A server program computes and streams results; any package, secret keys; costs money and maintenance.
The host just serves files; the reader’s browser runs the app. Free and fast; but WASM-only, no secret keys.
4.2 The three tools behind Shinylive
Three tools, one idea: run R in the browser with no server.
WebAssembly. A portable, sandboxed binary format. Sandboxed: it runs isolated from your computer and the page. Binary: it is compiled machine code in a .wasm file, not human-readable source. Browsers run it at near-native speed.
webR. The R interpreter compiled to WebAssembly, so R runs inside the browser.
Web assets library. A bundle of static JS/CSS/files a page downloads and runs; Shinylive itself is one.
4.3 What is WebAssembly?
WebAssembly (WASM) is a low-level bytecode: the machine-readable instructions a browser executes, run inside a secure sandbox. Think of it as a “portable CPU” that every browser already includes.
Languages like C, C++, Rust, R are compiled to .wasm files.
The browser runs them at near-native speed, isolated from the page.
It powers heavy in-browser apps: softs and web apps (Google Earth, AutoCAD, TensorFlow), games (Unity 3D engine, Doom, GameBoy), smartphone apps, and now statistical computing.
R is normally an installed binary. WASM lets the same R run wherever a browser runs: a phone, a colleague’s computer, GitHub Pages.
No server is needed to interpret the code; the browser is the runtime.
A package must already be compiled to WASM to run in the browser. Only packages listed in the webR repository (https://repo.r-wasm.org) work out of the box.
Note
Sweet spot. webR means “the same R you use at your desk, inside the reader’s tab”. The data never leaves their computer, and the code they see is the code you wrote.
4.6 What is a web assets library?
A web assets library is a set of static files (JavaScript, CSS, worker scripts, WASM binaries) a page downloads and runs. Shinylive is exactly one: it bundles the machinery to run Shiny in the browser via webR, as files you can drop on any static host.
On first open, the browser pulls down a bundle once:
webR / WASM binaries. The R interpreter run in the browser.
Shiny + Shinylive assets. The framework and glue.
Your app’s code & packages. Your UI/server, plus WASM packages.
Warning
This bundle is the main cost of Shinylive: a “cold start” may take a few seconds on first load; later visits are cached by the browser.
4.8 Route 1: a Shiny app, hosted static
Our first serverless goal: take a standalone Shiny app and host it on a static site. The route, in one picture:
flowchart LR
A[app.R] --> B[shinylive::export]
C[R packages → WASM] --> B
D[Shinylive web assets] --> B
B --> E[static site folder]
E --> F[any static host]
4.9 Export, in practice
Take the penguins app from the Shiny block. Exporting it is two lines:
App logic in a separate .R file? Paste it into the block with a Quarto shortcode, rather than source()-ing it: copy-paste plays far better with Shinylive’s in-browser file system.
```{shinylive-r}#| standalone: true{{< include app.R>}}```
4.14 Deployment: static hosts
A rendered report is plain files, so any static web host works. Quarto has native commands:
Go deeper:peeky (inspect web apps), webrarian (turn projects into shareable analyses), roxy.shinylive (apps in package docs), livelink (share apps via a URL).
5.6 Take-home project
Tonight: take one of your own analyses, small and non-sensitive:
Render it as a Quarto report with one embedded {shinylive-r} widget.
Host it on GitHub Pages (or Netlify).
Share the URL. Watch relatives click a slider and see a chart update, with no server anywhere.
5.7 The three tools
Quarto, Shiny, Shinylive.
5.8 Three tools, one goal
Three tools, one goal.
Quarto
A publishing system that grew up from R Markdown: the reproducible foundation of the report.
Shiny
A reactive UI + server model that turns analysis into interactive apps.
Shinylive
The same apps and reports running in the browser, no server needed.
5.9 The focus of today
Shinylive: serverless by design.
Shinylive
A web assets library built on webR + WASM. Export a standalone app with shinylive::export(), or embed a {shinylive-r} block in a Quarto report. Both run entirely in the reader’s browser and deploy on any static host, with no R server at all.