Quarto Workshop

Ravi Kalia

Quality Quarto Quest!

Objective

Learn to create professional blogs, reports, and more with Quarto.

Please read excellent Quarto Guide.

Agenda

Section Time
1 Intro: what Quarto is, why, the gallery 20 min
2 Install & environment + checkpoint 25 min
3 Markdown → Quarto: .md, .qmd, code blocks 40 min
4 Build a blog + checkpoint 35 min
5 Publish to GitHub Pages 20 min
6 Group exercise: collaborative blog 30 min
7 Recap & questions 10 min

Tip

Two checkpoints mark the points where everyone must be working before we move on. If you are stuck at a checkpoint, say so in chat — do not quietly fall behind. There is a “When It Breaks” slide near the end, and the fixes are usually 30 seconds.

If You Get Stuck

Setup problems should not cost you the rest of the session. There is a known-good blog you can clone at any point to rejoin:

git clone https://github.com/project-delphi/quarto-blog-starter my-blog
cd my-blog && conda env create -f environment.yml -p "$PWD/.conda"
conda activate "$PWD/.conda" && quarto preview

It contains a post with an executing {python} cell, so if it renders a chart, your whole toolchain works — Quarto, Python and the Jupyter kernel.

Tip

Use it. Sort your own environment out afterwards, or in the break — do not spend the workshop debugging conda while everyone else is writing posts.

Prerequisites

Tools — install these before the workshop

  • GitHub account
  • Quarto binary
  • GitHub CLI (gh) — used to create the repo and publish
  • VSCode + the Quarto extension
  • Git configured
  • Conda installed
  • POSIX shell — macOS, Linux, or WSL2 on Windows (not git bash, PowerShell or cygwin)

Skills

  • Creating conda environments
  • Comfort with Git & GitHub
  • Branching (GitHub Flow)
  • Posts answers and links in chat

Verify

quarto --version && gh auth status && conda --version

Windows: Set Up WSL2

Every command in this workshop assumes a POSIX shell. On Windows, get one via WSL2.

  1. In PowerShell as administrator, then reboot:

    wsl --install
  2. Open the “Ubuntu” terminal and install conda inside WSL, not on Windows:

    curl -fsSL -o miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
    bash miniconda.sh -b && ~/miniconda3/bin/conda init bash
  3. Install Quarto and gh inside WSL too.

Important

Work from the Linux home directory (~/code/...), not /mnt/c/.... Rendering across the Windows filesystem boundary is slow and hits permission errors.

What is Quarto?

Quarto is a modern, interactive publishing system built on pandoc:

  • Converts Markdown files to multiple formats:
    • html/rmarkdown/pdf
    • Reveal.js (slides, pptx)
    • Jupyter Notebooks (ipynb)
    • ePub, Typst, LaTex and more

origin: org-mode (Emacs) => Sweave / knitr => RMarkdown

Why Use Quarto?

  • Focus on reproducibility
  • Easy interactivity
  • Flexible output formats
  • Good processing of code blocks:
    • R
    • Python
    • Julia
    • ObservableJS

Underlying Technology

Quarto: A transpiler that converts markdown to various formats. The foundational technologies are:

  • Javascript (interactivity)
  • HTML (markdown)
  • CSS (fence divs)

Installation

  1. Install Quarto binary program
  2. Install the Quarto extension for VSCode:
    • Extensions > Search “Quarto” > Install
  3. Command line install of quarto cli

Exercise

One, Two & Three.

WARNING: Quarto is under active development!

Setting Up Your Environment

  1. Create a new directory for the workshop:

    mkdir -p ~/code/delete-me/quarto-play
    cd ~/code/delete-me/quarto-play
  2. Create a Conda environment:

    curl -fsSL -o environment.yml \
      https://raw.githubusercontent.com/project-delphi/quarto-workshop/main/environment.yml
    head -3 environment.yml   # expect YAML, not "<!DOCTYPE html>"
    conda env create -f environment.yml -p "$PWD/.conda"
    conda activate "$PWD/.conda"

    curl -f fails loudly on an HTTP error instead of saving the error page.

  3. .gitignore file to git ignore conda environment:

    curl -o .gitignore https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore
    echo -e "\n# Custom ignores\n.conda/\n.env/" >> .gitignore
  4. Initialize Git:

    git init --initial-branch=main
    git switch -c basic-markdown

Write Markdown

  1. Write a small example.md file:

    # Big Hello, Quarto!
    ## Smaller hello, quarto!
    ###### smallest hello, quarto!
    This is a demo of Markdown features:
    - **Bold text**
    - *Italic text*
    - [Links](https://quarto.org)
    - ![Image](https://picsum.photos/200/300)

Exercise

Write some markdown in a file.

Markdown Code Blocks

code blocks: formatted but not executed in markdown.

Use 3 backticks to create code blocks in markdown & the name of the language.

Python

def greet(name):
   print(f"Hello, {name}!")

R

greet <- function(name) {
   print(paste("Hello", name))
}

Javascript

function greet(name) {
   console.log(`Hello, ${name}!`);
}

Exercise

Add code blocks to your markdown file for Python, Javascript & R.

Convert Markdown

Transpile example.md using quarto:

quarto render example.md --to html
quarto render example.md --to ipynb

Exercise

Write and convert a markdown file to other format. Commit changes

Introducing qmd

Basic features:

  • document metadata YAML format file header:

    ---
    title: "blah"
    format: "html"
    date: "2025-01-22"
    ---
  • ::: fenced blocks are used for quarto & custom css divs and classes

    • ::: {.class}
    • ::: {#id}
    • ::: {.class #id}

Example qmd

  1. new branch git switch -c qmd-demo

  2. Write a small example.qmd file:

    ---   
    title: "Demo"
    format: html
    ---
    # Hello, Quarto!
    
    ::: {.panel-tabset}
    
    ### Tab 1
    Content for tab 1.
    ### Tab 2
    Content for tab 2.
    
    :::
  3. Render the file:

    quarto render example.qmd

Exercise

Write a .qmd file with fenced blocks.

Simple qmd Code Blocks

Quarto supports executing code blocks in markdown files.

Use curly braces {} to specify the execution language: {python}, {r}, {julia}.

Use curly braces with a period {.} to specify formatting options, no execution: {.python}, {.r}, {.julia}.

Python

def greet(name):
    print(f"Hello, {name}!")
greet("Quarto")
Hello, Quarto!

R

#| echo: true
#| warning: false
greet <- function(name) {
    print(paste("Hello", name))
}
greet("Quarto") 

“Quarto”

Exercise

Create a .qmd file with a Python and R code block.

#| Code Blocks

The #| syntax adds control over code block execution & display.

   #| echo: true
   #| warning: false
   def greet(name):
      print(f"Hello, {name}!")
Option Description
#|eval Evaluate the code chunk.
#|echo Include the source code in output.
#|output Include the results of executing the code in the output (true, false, or asis).
#|warning Include warnings in the output.
#|error Include errors in the output.
#|include Catch all for preventing any output (code or results) from being included.

Exercise

Create a .qmd file with a Python code block using the #| options like echo, warning, and eval.

Blog: Set Up the Environment First

The environment provides the toolchain, so build it before creating the project.

  1. Make a directory and get the environment file:

    mkdir -p ~/code/delete-me/quarto-blog && cd ~/code/delete-me/quarto-blog
    curl -fsSL -o environment.yml \
      https://raw.githubusercontent.com/project-delphi/quarto-workshop/main/environment.yml
    head -3 environment.yml   # expect YAML, not "<!DOCTYPE html>"
    conda env create -f environment.yml -p "$PWD/.conda"
  2. Activate it:

    conda activate "$PWD/.conda"

Note

The Python-only environment solves in a couple of minutes. R is a separate, optional environment used later — see the RStudio section.

Checkpoint: Is Your Toolchain Right?

Run all four. Do not move on until each one is right.

which quarto                 # expect a path, not "not found"
quarto check                 # expect Python + Jupyter both OK
which python                 # expect .../quarto-blog/.conda/bin/python
jupyter kernelspec list      # expect python3 -> your .conda, NOT some other project

The last one catches the sneakiest failure: an old user-level kernel can shadow your environment, so import matplotlib fails even though it is installed. If python3 points elsewhere, prefix commands with JUPYTER_PATH="$PWD/.conda/share/jupyter".

Exercise

Post your quarto check output in chat if anything looks off.

Create the Blog Project

quarto create project blog . --no-open --no-prompt
git init --initial-branch=main && git switch -c blog-setup

--no-open skips launching an editor, --no-prompt skips the confirmation — without them the command stops and waits for input.

Quarto generates:

Path What it is
_quarto.yml Site config: title, navbar, theme. Edit this — never replace it.
index.qmd The listing page that indexes your posts
posts/ One folder per post, each with an index.qmd
posts/_metadata.yml Defaults applied to every post
styles.css Your CSS overrides (note: styles, not style)
about.qmd An about page wired into the navbar

Exercise

Open _quarto.yml and change the site title. Re-render and find your change.

Writing a Blog Post

Posts live in their own folder. Create posts/my-first-post/index.qmd (the template already ships a posts/welcome/, so pick a new name):

---
title: "My First Blog Post"
date: 2025-01-20
---

# Welcome!

My first Quarto post. The chart below is generated when the post renders.

```{python}
import matplotlib.pyplot as plt

years = [2021, 2022, 2023, 2024]
posts = [1, 4, 9, 16]

plt.plot(years, posts, marker="o")
plt.title("Posts per year")
plt.show()
```

{python} has no dot — so the code runs and its output is part of the post. Write {.python} and you get syntax highlighting and nothing else.

Preview & Render Blog Post

The preview is a live preview of the blog post in the browser. The render command generates the output file.

  1. Preview:

    quarto preview posts/welcome.qmd
  2. Render:

    quarto render posts/welcome.qmd

Using Jupyter To Blog

  • Convert a jupyter notebook to a blogpost using quarto.
  • Quarto first converts the notebook to qmd, and from there to the desired format.
  • Just put YAML metadata at the top of the notebook in raw text format.
  • Save the notebook in the posts/ directory.
  • quarto preview & quarto render the notebook.

Exercise

Write and convert a jupyter notebook to a blog post.

Publish to GitHub Pages

Publishing is a command, not configuration. There is no publish: key in _quarto.yml.

  1. Commit your work:

    git add .
    git commit -m "Initial blog setup"
  2. Create the GitHub repo — this also creates the origin remote:

    gh auth status || gh auth login
    gh repo create my-blog --public --source=. --push
  3. Publish:

    quarto publish gh-pages

Quarto renders the site, creates the gh-pages branch, adds .nojekyll, pushes, and prints your URL. No repo settings to click through.

  1. Fold your work back into main (GitHub flow):

    git switch main && git merge blog-setup
    git push -u origin main

Exercise

Publish your blog and post the URL in chat.

Blog with RStudio

Optional section. Needs RStudio Desktop installed, plus the R environment (not in the main one) — create it first:

curl -fsSL -o environment-r.yml \
  https://raw.githubusercontent.com/project-delphi/quarto-workshop/main/environment-r.yml
conda env create -f environment-r.yml -p "$PWD/.conda-r" && conda activate "$PWD/.conda-r"
  1. Run which R after conda activated environment
  2. Launch RStudio from the activated shell so it inherits the environment:
    • macOS: open -na rstudio
    • Linux / WSL2: rstudio & (requires RStudio Desktop installed)
  3. Create new project from RStudio UI
  4. Select Quarto Blog project type.
  5. Create a new post and render it.
  6. Push to GitHub.

Exercise

Create a quarto blog post using RStudio.

When It Breaks

You see It means Do this
quarto: command not found Not installed, or env not activated which quarto, then quarto check
CondaError: Run 'conda init' Shell not set up for conda conda init "$(basename $SHELL)", restart shell
conda env create fails parsing YAML You downloaded an error page, not the file head -3 environment.yml — expect name:
ModuleNotFoundError for a package you know is installed A user-level Jupyter kernel is shadowing your env jupyter kernelspec list; prefix with JUPYTER_PATH="$PWD/.conda/share/jupyter"
which R points outside your env R env not created or not activated R is optional — see the RStudio section
Address already in use on preview An old preview is still running quarto preview --port 4201
gh: please run: gh auth login Not authenticated gh auth login
Site published but 404s Pages is still building Wait a minute; check the repo’s Actions tab

The fourth row is the sneaky one: the package really is installed, just not in the interpreter Quarto is using.

Still stuck after two minutes? Clone quarto-blog-starter (see “If You Get Stuck”) and keep moving — debug afterwards.

Exercise

Post the exact error text in chat — not a screenshot of your whole screen.

Group Exercise: Collaborative Blog

  1. Form groups of 3–4 persons, have one person who is advanced in github flow.

  2. Each group member creates a branch:

    git checkout -b member_initials/feature-branch-name
  3. Write posts in the posts/ directory.

  4. Merge branches to main.

  5. Push and share your blog URL.

Recap

  • Quarto combines flexibility and reproducibility.
  • Create professional blogs, reports, and more.
  • Practice collaborative workflows with GitHub.

Next Steps

  • Explore advanced Quarto features.
  • Build your personal portfolio site!

Questions?