Document a Python CLI with MkDocs

Build a live documentation site for a Python CLI tool using MkDocs Material.

Introduction

30 Second Summary

You know that feeling when you find a tool's documentation and it actually tells you what you need to know? Clear examples, a search bar that works, and pages organized by what you're trying to do. That kind of documentation doesn't happen by accident.

In this project, you will build a professional documentation site for a Python CLI tool using MkDocs Material. You will structure your docs using the Diataxis framework, add rich formatting features, and deploy the site live to GitHub Pages with automatic updates on every push.

What You'll Build

By the end, you will have a live documentation site on the internet that you can send to anyone, and it will look and feel like documentation from companies like Stripe or Cloudflare.

By the end of this project, you'll have:

  • A live documentation site you can share with anyone, complete with instant search, dark/light mode toggle, and tabbed navigation.
  • Four documentation pages written in distinct Diataxis styles (tutorial, how-to, reference, explanation) that demonstrate you understand how different readers need different content.
  • A CI/CD pipeline using GitHub Actions that automatically publishes your latest changes the moment you push to GitHub.
  • Rich content features like annotated code blocks with clickable explanations, coloured callout boxes, and tabbed examples for different platforms.
  • Secret Mission: Add architecture diagrams to your docs using Mermaid.js, rendered from plain text in your Markdown files.

Are there any prerequisites?

You should have Python installed, a GitHub account, and be comfortable using the terminal, Git, and Markdown. No prior experience with documentation tooling is needed.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. Good documentation turns a script only you can use into a tool anyone can pick up.

Set Up MkDocs Material and Create Your Docs Skeleton

You have MkDocs Material installed and a project directory ready to go. The next piece is the actual project structure that MkDocs expects before it can build anything.

MkDocs uses a specific layout: a mkdocs.yml config file at the root and a docs/ folder for your Markdown pages. In this step, you will scaffold that layout, configure the Material theme with navigation, and get a live-reloading docs site running in your browser.

In this step, get ready to:
  • Scaffold the MkDocs project structure with a single command.
  • Configure the Material theme, colour palette, and navigation in mkdocs.yml.
  • Start a live-reloading dev server and see your docs site in the browser.
Scaffold the MkDocs project

MkDocs provides a scaffold command that creates the base project structure for you. It generates the config file and docs folder in one shot.

  • Scaffold your MkDocs project by running this command in your terminal:
mkdocs new .

What does this command do?

The mkdocs new . command creates two things inside your current directory: a mkdocs.yml configuration file (where all your site settings live) and a docs/ folder containing a default index.md page.

  • Confirm the files were created by running:
ls

You should see docs and mkdocs.yml listed in the output.

Don't see both items?

Make sure your terminal is inside the wordfreq-docs folder. Run pwd to check your current location.

If you see a message that the project already exists, the scaffold ran previously. You can safely continue to the next substep.

help me troubleshoot why mkdocs new . didn't create files

Configure the Material theme and navigation

The default mkdocs.yml is bare-bones. You need to replace it with a full configuration that activates the Material theme, sets up a colour palette with dark/light mode, and defines your navigation structure.

  • Open mkdocs.yml in your code editor and delete all its current content.
  • Paste in the following theme configuration:
# Site metadata
site_name: WordFreq Documentation
site_url: https://username.github.io/wordfreq-docs/

# Theme configuration
theme:
  name: material
  palette:
    - scheme: default
      primary: indigo
      accent: indigo
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode
    - scheme: slate
      primary: indigo
      accent: indigo
      toggle:
        icon: material/brightness-4
        name: Switch to light mode
  features:
    - navigation.instant
    - navigation.tabs
    - navigation.sections
    - search.suggest
    - search.highlight
    - content.code.copy
    - content.code.annotate

What does this configure?

  • site_name sets the title shown in the browser tab and header.
  • palette defines two colour schemes (light and dark) with toggle buttons so readers can switch.
  • features enables instant navigation (no full page reloads), tabbed top nav, search autocomplete, and a copy button on code blocks.

The site_url value uses a placeholder username. You will update this with your real GitHub username when you deploy in a later step.

  • Below the theme configuration (leave one blank line), paste the navigation structure:
# Navigation structure
nav:
  - Home: index.md
  - Tutorial: tutorial.md
  - How-To Guides: how-to.md
  - Reference: reference.md

What does the nav section do?

The nav key defines your sidebar and top-tab navigation. Each entry maps a display label to a Markdown file inside docs/.

The Tutorial, How-To Guides, and Reference pages do not exist yet. MkDocs will show them as empty placeholders until you create them in later steps.

  • Save mkdocs.yml.

✔️ Awesome, I've got everything!

Make sure you saved the file. Your mkdocs.yml should have the theme block at the top and the nav block at the bottom, separated by a blank line.

ⓧ I'd like to double check the full code

Your mkdocs.yml should contain two sections in this order:

  • The site metadata and theme configuration block (starting with site_name and ending with content.code.annotate).
  • A blank line, then the navigation block (starting with nav: and listing all four pages).

Scroll up and confirm both chunks are present in your file, in that order. Remove any leftover content from the original scaffold.

Write the home page and start the dev server

The scaffold created a generic docs/index.md with placeholder text. You will replace it with a proper welcome page that links to each section of your documentation.

  • Open docs/index.md in your code editor and replace all its content with:
# WordFreq Documentation

Welcome to the documentation for **WordFreq**, a command-line tool that counts word frequencies in text files.

## Quick Links

| I want to... | Go to... |
|---|---|
| Learn how to use WordFreq from scratch | [Tutorial](tutorial.md) |
| Solve a specific problem | [How-To Guides](how-to.md) |
| Look up CLI flags or function signatures | [Reference](reference.md) |

## Installation

```bash
pip install -e .
```

Or run directly:

```bash
python src/wordfreq.py yourfile.txt
```

What does this page do?

This is your documentation home page. The Quick Links table gives readers a way to jump directly to the right section based on what they need.

This follows a common pattern in developer docs: orient the reader immediately instead of making them hunt through a sidebar.

  • Save docs/index.md.

✔️ Awesome, I've got everything!

Make sure you saved the file. You are ready to start the dev server.

ⓧ I'd like to double check the full code

# WordFreq Documentation

Welcome to the documentation for **WordFreq**, a command-line tool that counts word frequencies in text files.

## Quick Links

| I want to... | Go to... |
|---|---|
| Learn how to use WordFreq from scratch | [Tutorial](tutorial.md) |
| Solve a specific problem | [How-To Guides](how-to.md) |
| Look up CLI flags or function signatures | [Reference](reference.md) |

## Installation

```bash
pip install -e .
```

Or run directly:

```bash
python src/wordfreq.py yourfile.txt
```

Before you start the server, what do you expect to see when MkDocs builds your site with the Material theme?

  • Start the live-reloading dev server by running:
mkdocs serve
  • Open your browser and go to http://127.0.0.1:8000.

You should see your WordFreq Documentation site with the Material theme applied: an indigo header bar, a navigation sidebar on the left, a search bar at the top, and your Quick Links table on the home page.

What is happening in your terminal?

The mkdocs serve command builds your Markdown files into HTML and starts a local web server. It watches your files for changes and automatically rebuilds when you save.

Your terminal is now occupied by the server process. To run other commands later, open a new terminal tab or window. Press Ctrl+C to stop the server when you are done.

Don't see the Material theme?

If you see a plain-looking site without the indigo header, check that mkdocs.yml has name: material under the theme: key. YAML is indentation-sensitive, so name must be indented two spaces under theme.

If you see an error about port 8000 already in use, another process is using that port. Stop it with Ctrl+C in the other terminal, or run mkdocs serve -a localhost:8001 to use a different port.

If you see a build warning about pages not found (tutorial.md, how-to.md, reference.md), that is expected. Those files do not exist yet and will be created in later steps.

help me fix my mkdocs serve issues

Your documentation skeleton is alive. Next up, you will write your first real documentation page: a quickstart tutorial that teaches someone how to use WordFreq from scratch.

Write a Quickstart Tutorial

Your docs site is running, but click through the navigation sidebar and you'll find empty placeholder pages. The first page to fill is the most important one: the tutorial.

A tutorial is one of four documentation types defined by the Diataxis framework. It's learning-oriented, guiding a new user through their first experience with concrete steps and minimal explanation. Before you can write a tutorial, you need something real to document.

In this step, get ready to:
  • Create the Python CLI tool you'll be documenting.
  • Write a Diataxis-style quickstart tutorial for new users.
  • Preview the tutorial page in your browser.
Create the word-frequency CLI tool

You need a real tool to document. You'll create a small Python script that counts word frequencies in text files.

  • Create a src/ directory inside your project by running this command:
mkdir src
  • Create the file src/wordfreq.py and add the imports and core counting function:
"""Word Frequency Counter - A simple CLI tool for text analysis."""

import argparse
import sys
from collections import Counter
from pathlib import Path


def count_words(text: str, top_n: int = 10) -> list[tuple[str, int]]:
    """Count word frequencies in text and return top N results.

    Args:
        text: Input text to analyze.
        top_n: Number of top words to return. Defaults to 10.

    Returns:
        List of (word, count) tuples sorted by frequency descending.
    """
    words = text.lower().split()
    counter = Counter(words)
    return counter.most_common(top_n)

What does this code do?

  • The imports bring in argparse for CLI argument parsing, Counter for efficient counting, and Path for file operations.
  • The count_words() function lowercases text, splits it on whitespace, and returns the most common words as a sorted list of tuples.
  • Below the count_words function, add the CLI entry point:
def main():
    """CLI entry point for the word frequency counter."""
    parser = argparse.ArgumentParser(
        description="Count word frequencies in a text file."
    )
    parser.add_argument("file", type=Path, help="Path to the text file to analyze")
    parser.add_argument(
        "-n", "--top", type=int, default=10, help="Number of top words to display (default: 10)"
    )
    parser.add_argument(
        "--json", action="store_true", help="Output results in JSON format"
    )
    args = parser.parse_args()

    if not args.file.exists():
        print(f"Error: File '{args.file}' not found.", file=sys.stderr)
        sys.exit(1)

    text = args.file.read_text()
    results = count_words(text, args.top)

    if args.json:
        import json
        print(json.dumps([{"word": w, "count": c} for w, c in results], indent=2))
    else:
        print(f"Top {args.top} words in '{args.file.name}':")
        print("-" * 40)
        for i, (word, count) in enumerate(results, 1):
            print(f"  {i:2}. {word:<20} {count:>5}")


if __name__ == "__main__":
    main()

What does this code do?

  • The main() function uses argparse to accept a file path, an optional -n flag for limiting results, and a --json flag for machine-readable output.
  • If the file doesn't exist, it prints an error and exits. Otherwise it reads the file, counts words, and prints results in either a formatted table or JSON.
  • The if __name__ == "__main__" guard means the script runs main() only when executed directly.
  • Save src/wordfreq.py.
  • Test the tool by creating a sample file and running it:
echo "hello world hello test world hello" > test.txt
python src/wordfreq.py test.txt

You should see a formatted table showing word frequencies, with "hello" at the top with a count of 3.

Seeing 'No such file or directory'?

  • Make sure you're in the wordfreq-docs project folder (the one containing mkdocs.yml).
  • Confirm src/wordfreq.py exists by running ls src/.
  • Check for typos in the filename path. Help me debug why my wordfreq.py script can't be found
  • Delete the test file now that you've confirmed the tool works:
rm test.txt

✔️ Awesome, I've got everything!

Great. Double check you've saved src/wordfreq.py and your tool ran successfully.

ⓧ I'd like to double check the full code

Your src/wordfreq.py is over 30 lines, so confirm both chunks are present in order:

  • The module docstring, four imports (argparse, sys, Counter, Path), and the count_words() function.
  • The main() function with the argument parser, file check, word counting logic, JSON/table output, and the if __name__ == "__main__" guard at the bottom.
Write the tutorial page

The Diataxis framework says tutorials should be learning-oriented: concrete steps, minimal explanation, and a clear outcome the reader achieves by following along. Your tutorial will walk a first-time user through running WordFreq on a sample file.

  • Open docs/tutorial.md and replace any placeholder content with the tutorial introduction and first step:
# Quickstart Tutorial

This tutorial walks you through using WordFreq for the first time. By the end, you will have analyzed a text file and seen a ranked list of its most frequent words.

## What you'll accomplish

You will create a sample text file, run WordFreq against it, and interpret the output.

## Step 1: Create a sample file

Create a file called `sample.txt` with some text:

```bash
echo "the cat sat on the mat the cat liked the mat" > sample.txt
```

What makes this a Diataxis tutorial?

  • It opens with a clear statement of what the reader will accomplish.
  • Each step is a concrete action the reader performs, not an abstract explanation.
  • The sample data is provided for them so they can follow along exactly without making decisions.
  • Save docs/tutorial.md and check your browser at http://127.0.0.1:8000/tutorial/.

You should see the "Quickstart Tutorial" heading and the Step 1 section with a code block.

  • Now add the section where the reader runs WordFreq and sees their first result. Append this below the Step 1 content in docs/tutorial.md:
## Step 2: Run WordFreq

```bash
python src/wordfreq.py sample.txt
```

You should see output like this:

```
Top 10 words in 'sample.txt':
----------------------------------------
   1. the                      4
   2. cat                      2
   3. mat                      2
   4. sat                      1
   5. on                       1
   6. liked                    1
```

!!! tip
    WordFreq converts all text to lowercase before counting, so "The" and "the" are treated as the same word.

## Step 3: Limit the results

Use the `-n` flag to show only the top 3 words:

```bash
python src/wordfreq.py sample.txt -n 3
```

```
Top 3 words in 'sample.txt':
----------------------------------------
   1. the                      4
   2. cat                      2
   3. mat                      2
```

What does this section demonstrate?

  • Step 2 gives the reader their first visible result, building confidence.
  • Step 3 introduces the -n flag with a minimal, focused example.
  • The !!! tip block is MkDocs admonition syntax. It won't render as a styled box yet because the extension isn't enabled. You'll fix that in the next step.
  • Save and check the browser. You should see Steps 2 and 3 appear with their code blocks and example output.
  • Finally, add the JSON output section and closing navigation. Append this at the bottom of docs/tutorial.md:
## Step 4: Get JSON output

For programmatic use, add the `--json` flag:

=== "Command"

    ```bash
    python src/wordfreq.py sample.txt -n 3 --json
    ```

=== "Output"

    ```json
    [
      {"word": "the", "count": 4},
      {"word": "cat", "count": 2},
      {"word": "mat", "count": 2}
    ]
    ```

!!! warning
    The `--json` flag outputs to stdout. Make sure to redirect if you want to save to a file:
    ```bash
    python src/wordfreq.py sample.txt --json > results.json
    ```

## Next steps

Now that you have the basics, check out the [How-To Guides](how-to.md) for specific tasks, or the [Reference](reference.md) for a complete list of options.

What does this section demonstrate?

  • Step 4 shows an advanced feature (JSON output) for readers who need machine-readable data.
  • The === "Command" and === "Output" blocks are tabbed content syntax for MkDocs Material. Like the admonitions, they won't render as interactive tabs until you enable the extension in the next step.
  • The "Next steps" section links readers to other documentation pages, keeping them moving through your site.
  • Save docs/tutorial.md.

Page not updating in the browser?

  • Make sure mkdocs serve is still running in your terminal. If it stopped, run it again from your project folder.
  • Check that the file is saved as docs/tutorial.md (inside the docs/ folder, not the project root).
  • Help me figure out why my MkDocs tutorial page isn't showing updates

✔️ Awesome, I've got everything!

Great. Double check you've saved docs/tutorial.md with all four steps included.

ⓧ I'd like to double check the full code

Your docs/tutorial.md is long, so confirm all three chunks are present in order:

  • The title ("Quickstart Tutorial"), the "What you'll accomplish" section, and Step 1 (create a sample file with the echo command).
  • Step 2 (run WordFreq with expected output and a !!! tip block) and Step 3 (limit results with -n 3).
  • Step 4 (JSON output with tabbed === "Command" and === "Output" blocks, a !!! warning block) and the "Next steps" section linking to other pages.
Preview the complete tutorial

Before you run, what do you expect the tutorial page to look like? Will the admonitions and tabs render as styled components, or as plain text?

  • Switch to your browser and navigate to http://127.0.0.1:8000/tutorial/.

You should see a complete quickstart page with proper headings, code blocks, and a logical step-by-step flow that a new user could follow. The !!! tip and === "Command" blocks appear as plain text right now because the Markdown extensions aren't enabled yet.

Why don't the admonitions render yet?

MkDocs Material requires you to explicitly list each Markdown extension in mkdocs.yml. Without the admonition and pymdownx.tabbed extensions enabled, the syntax shows up as raw text. You'll enable these in the next step and watch the page transform.

Your documentation site now has real content that a new user could follow from start to finish. Next up, you'll enable the Markdown extensions that transform those raw !!! and === blocks into polished, interactive components.

Add Rich Content Features

Your quickstart tutorial is written, but take a closer look at how it renders in the browser right now. The !!! tip and === "Command" syntax you used in Step 2 shows up as raw text instead of polished callout boxes and tabbed panels.

MkDocs Material supports powerful Markdown extensions that transform this syntax into coloured admonition boxes, clickable code annotations, and tabbed content. These extensions need to be explicitly enabled in your configuration file before they render.

In this step, get ready to:
  • Enable Markdown extensions in mkdocs.yml.
  • See admonitions, tabs, and code annotations render as rich content.
  • Verify the tutorial page displays all formatting features correctly.
Enable Markdown extensions in mkdocs.yml

MkDocs Material bundles extensions from the Python Markdown Extensions library, but they're inactive until you declare them in your config. Each extension unlocks a specific feature: admonitions for callout boxes, tabbed for switchable panels, highlight for syntax colouring.

  • In mkdocs.yml, scroll to the bottom of the file (below the nav section) and add this markdown_extensions block:
markdown_extensions:
  - admonition
  - pymdownx.details
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
  - pymdownx.highlight:
      anchor_linenums: true
  - pymdownx.inlinehilite
  - attr_list
  - md_in_html

What does each extension do?

  • admonition turns !!! tip syntax into coloured callout boxes (tip, warning, note, etc.).
  • pymdownx.details makes admonitions collapsible with ??? tip syntax.
  • pymdownx.superfences allows nested code blocks and is required for tabs and diagrams.
  • pymdownx.tabbed renders === "Tab Name" syntax as switchable tab panels.
  • pymdownx.highlight and pymdownx.inlinehilite add line numbers and inline syntax highlighting.
  • Save mkdocs.yml.
  • Switch back to the terminal where mkdocs serve is running. You should see it rebuild automatically with no errors.

Seeing a YAML error in the terminal?

YAML is sensitive to indentation. Make sure each nested key is indented with exactly two spaces (not tabs). The alternate_style: true line under pymdownx.tabbed must be indented 6 spaces from the left margin.

help me fix a YAML indentation error in my mkdocs.yml markdown_extensions section

✔️ Awesome, I've got everything!

Great. Double check you've saved mkdocs.yml and confirmed the dev server rebuilt without errors.

ⓧ I'd like to double check the full code

Your mkdocs.yml should now have three top-level sections in this order:

  • The site_name, site_url, and theme block (with palette, features) from the previous step.
  • The nav block listing Home, Tutorial, How-To Guides, and Reference.
  • The new markdown_extensions block you just added, listing all 8 extensions.

Scroll up and confirm the extensions block matches the code above exactly.

Understand the rich formatting syntax in your tutorial

The tutorial you wrote in the previous step already uses the special syntax that these extensions unlock. Now that the extensions are active, that syntax renders as rich interactive content instead of raw text.

What syntax is already in your tutorial?

Your docs/tutorial.md already contains three types of rich formatting:

  • Admonitions like !!! tip and !!! warning render as coloured callout boxes with icons.
  • Tabbed content using === "Command" and === "Output" creates switchable panels so readers can toggle between code and its result.
  • Code annotations use the # (1)! comment syntax inside code blocks to add numbered popup explanations. This feature is enabled through the content.code.annotate theme feature already in your config.

When to use each feature

Use admonitions for tips, warnings, and notes that a reader should notice but that aren't part of the main instruction flow. Use tabs when the same concept has platform-specific commands (macOS vs Windows) or when you want to show a command alongside its output without cluttering the page.

Preview the rich content in your browser

Before you look at the browser, what do you think the !!! tip block in your tutorial will look like now that extensions are enabled?

  • Return to the browser at http://127.0.0.1:8000/tutorial/ and refresh the page.

You should now see three distinct rich content features on the tutorial page:

  • A green tip admonition box with a lightbulb icon explaining the lowercase behaviour.
  • An orange warning admonition box about stdout redirection.
  • A tabbed panel under "Step 4: Get JSON output" with Command and Output tabs that you can click to switch between.

Still seeing raw text like !!! tip?

The extensions aren't loading. Check that markdown_extensions: is at the top level of mkdocs.yml (no indentation before it), not nested inside theme: or nav:. Also check the terminal for error messages from the dev server.

help me debug why my MkDocs Material markdown extensions are not rendering

Your documentation now has professional formatting that matches what you see on sites like Stripe's docs or Google Cloud's documentation. Next up, you'll write the reference and how-to pages to complete the full Diataxis documentation set.

Write Reference and How-To Pages

Your documentation site has a tutorial page that teaches beginners how to use WordFreq. Tutorials are great for newcomers, but working developers have different needs.

Sometimes a developer needs to look up a function signature quickly. Sometimes they need step-by-step directions for a specific task without any teaching. The Diataxis framework addresses this by defining four documentation types: tutorials, how-to guides, reference, and explanation. In this step, you will write the reference and how-to pages to serve these distinct user needs.

In this step, get ready to:
  • Write a reference page documenting the CLI flags and Python API.
  • Write a how-to guides page with task-oriented directions for specific problems.
  • Verify both pages render correctly and are accessible from the navigation sidebar.
Write the CLI reference section

A Diataxis reference page is information-oriented. It describes the machinery accurately and completely, like a dictionary entry. No opinions, no teaching, no steps to follow.

  • Open docs/reference.md in your editor and replace all its contents with the following CLI reference section:
# Reference

Technical descriptions of the WordFreq CLI and Python API.

## CLI Usage

```
python src/wordfreq.py [-h] [-n TOP] [--json] file
```

### Positional Arguments

| Argument | Description |
|---|---|
| `file` | Path to the text file to analyze |

### Options

| Flag | Default | Description |
|---|---|---|
| `-h`, `--help` | - | Show help message and exit |
| `-n`, `--top` | `10` | Number of top words to display |
| `--json` | `false` | Output results in JSON format |

What does this code do?

This is pure reference-style documentation. It opens with the exact CLI usage syntax so a developer can copy it at a glance.

The tables lay out every flag with its default value and a one-line description. No examples, no persuasion. Just facts.

  • Save docs/reference.md.
  • Switch to your browser. You should see the Reference page now shows a CLI usage block and two formatted tables for positional arguments and options.

Tables not rendering?

Make sure the pipe characters | line up and there are no extra spaces before the first pipe on each row. MkDocs requires the separator row (|---|---|) immediately after the header row.

Why aren't my Markdown tables showing up correctly?

Add the Python API reference section

Developers who import WordFreq as a library need to know the function signatures, parameter types, and return values. This section documents the Python API the same way the CLI section documented the flags.

  • In docs/reference.md, add the following Python API section below the Options table:

## Python API

### `count_words(text, top_n=10)`

Count word frequencies in text and return top N results.

**Parameters:**

| Parameter | Type | Default | Description |
|---|---|---|---|
| `text` | `str` | (required) | Input text to analyze |
| `top_n` | `int` | `10` | Number of top words to return |

**Returns:** `list[tuple[str, int]]` - List of (word, count) tuples sorted by frequency descending.

**Example:**

```python
from src.wordfreq import count_words

results = count_words("the cat sat on the mat", top_n=3)
# [('the', 2), ('cat', 1), ('sat', 1)]
```

### `main()`

CLI entry point. Parses command-line arguments and prints results to stdout. Exits with code 1 if the specified file does not exist.

What does this section cover?

  • The count_words function gets a full parameter table with types, defaults, and a usage example.
  • The main() entry point gets a brief description since developers typically use the function, not the entry point, when importing.
  • Save docs/reference.md.
  • Refresh your browser. You should now see the Python API section beneath the CLI tables, with a parameters table, a return type, and a highlighted Python code example.

Code example not highlighting?

Check that the code fence specifies the language (```python not just ```). The pymdownx.highlight extension in your mkdocs.yml handles the syntax colouring, but it needs the language hint.

Help me fix my code highlighting in MkDocs.

✔️ Awesome, I've got everything!

Great. Make sure you have saved docs/reference.md before moving on.

ⓧ I'd like to double check the full code

Scroll up and confirm your docs/reference.md contains these sections in order:

  • The # Reference heading with the CLI Usage code block and Positional Arguments table.
  • The Options table with three flags (-h, -n, --json).
  • The Python API section with the count_words parameters table, return type, example, and the main() description.
Write the how-to guides page

A Diataxis how-to guide is goal-oriented. It gives practical directions for solving a specific problem. No teaching, no background. The reader already knows the tool and just needs directions to accomplish a task.

  • Open docs/how-to.md in your editor and replace all its contents with the following:
# How-To Guides

Practical directions for specific tasks. Each guide assumes you already have WordFreq working.

## How to analyze multiple files

To analyze all `.txt` files in a directory, use a shell loop:

=== "Linux/macOS"

    ```bash
    for f in docs/*.txt; do
      echo "=== $f ==="
      python src/wordfreq.py "$f" -n 5
      echo ""
    done
    ```

=== "Windows (PowerShell)"

    ```powershell
    Get-ChildItem *.txt | ForEach-Object {
      Write-Host "=== $($_.Name) ==="
      python src/wordfreq.py $_.FullName -n 5
      Write-Host ""
    }
    ```

What does this guide demonstrate?

This guide uses MkDocs Material's tabbed content (the === "Tab Name" syntax) to show platform-specific shell loops. A Linux/macOS user sees a Bash for-loop. A Windows user sees the PowerShell equivalent.

Notice the tone: no explanation of what a for-loop is. How-to guides assume the reader already knows the basics and just needs the recipe.

  • Save docs/how-to.md.
  • Switch to your browser and click How-To Guides in the navigation. You should see a heading, an intro line, and the tabbed code blocks for Linux/macOS and Windows.

Tabs not switching?

Tabbed content requires exactly 4 spaces of indentation for the content under each === line. If tabs render as plain text, check that the code fences are indented with spaces (not a tab character) and that pymdownx.tabbed is listed in your mkdocs.yml extensions.

Help me fix my tabbed content in MkDocs Material.

  • Now add the remaining two how-to guides below the multiple files section in docs/how-to.md:

## How to export results to JSON

Run WordFreq with the `--json` flag and redirect output to a file:

```bash
python src/wordfreq.py myfile.txt --json > results.json
```

!!! note
    The JSON output is an array of objects, each with `word` and `count` keys. You can pipe this into `jq` for further processing:
    ```bash
    python src/wordfreq.py myfile.txt --json | jq '.[0:3]'
    ```

## How to use WordFreq in a Python script

Import the `count_words` function directly:

```python
from src.wordfreq import count_words

text = open("myfile.txt").read()
results = count_words(text, top_n=5)

for word, count in results:
    print(f"{word}: {count}")
```

What do these guides cover?

  • The JSON export guide shows the exact command and uses an admonition (!!! note) to add a supplementary tip about piping into jq.
  • The Python script guide shows how to use count_words as an importable function. Three lines, no explanation beyond the code.
  • Save docs/how-to.md.
  • Refresh your browser. Scroll down on the How-To Guides page. You should now see three guides total: the tabbed shell-loop section, a JSON export section with a coloured note admonition, and a Python script section with a highlighted code block.

Admonition not rendering as a coloured box?

The !!! note syntax requires the admonition extension in mkdocs.yml. Also check that the admonition body is indented with exactly 4 spaces. A tab character or 2 spaces will break it.

Help me debug my admonitions in MkDocs Material.

✔️ Awesome, I've got everything!

Great. Confirm you have saved docs/how-to.md before moving on.

ⓧ I'd like to double check the full code

Scroll up and confirm your docs/how-to.md contains these sections in order:

  • The # How-To Guides heading with an intro line.
  • The "How to analyze multiple files" section with Linux/macOS and Windows (PowerShell) tabs.
  • The "How to export results to JSON" section with a bash command and a !!! note admonition.
  • The "How to use WordFreq in a Python script" section with a Python code block.
Verify the navigation sidebar

Before you run this final check, think about what you expect to see. You have three content pages now. Do you think each one will appear in the sidebar with a distinct look and feel?

  • Click through each page in the navigation sidebar: Tutorial, How-To Guides, and Reference.

You should see three distinct pages, each with a noticeably different tone. The tutorial walks someone through steps with tips and warnings. The how-to page gives direct recipes with no hand-holding. The reference page lists facts in tables with no narrative.

Why do these pages feel so different?

Each page serves a different user need in the Diataxis framework. A tutorial is learning-oriented. A how-to guide is goal-oriented. A reference page is information-oriented. The tone follows the purpose.

This is what professional documentation looks like. One site, multiple page types, each designed to answer a different kind of question a developer might bring.

Your documentation now covers three of the four Diataxis types. Users can learn (tutorial), solve problems (how-to), and look things up (reference). Next up, you will take this site live on the internet so anyone can access it.

Deploy to GitHub Pages with GitHub Actions

Your documentation site currently lives only on your local machine. Nobody else can see it.

In this step, you'll push your project to GitHub and set up a CI/CD pipeline that automatically builds and deploys your docs to GitHub Pages every time you push a change. This is the docs-as-code workflow used by professional engineering teams.

In this step, get ready to:
  • Create a GitHub Actions workflow for automated deployment.
  • Push your project to GitHub and configure GitHub Pages.
  • Verify your live documentation site is accessible on the internet.
Create a GitHub repository and add the workflow file

You need a remote repository on GitHub to host your code, and a workflow file that tells GitHub Actions how to build and deploy your docs.

  • Open your browser and go to github.com.
  • Click the + icon in the top-right corner, then click New repository.
  • Enter wordfreq-docs as the repository name.
  • Keep the repository set to Public.
  • Leave all checkboxes unchecked (no README, no .gitignore, no license).
  • Click Create repository.

You should see the quick setup page with instructions for pushing an existing repository. That confirms the empty repo is ready.

Why leave it empty?

You already have a local Git repository with all your files. Initializing the remote with a README would create a conflict when you push. Starting empty keeps the process smooth.

  • Switch back to your terminal. If mkdocs serve is still running, press Ctrl+C to stop it.
  • Create the workflow directory structure by running this command:
mkdir -p .github/workflows
  • Confirm the directory was created by running:
ls .github/workflows

The command should run without error, showing an empty directory. This is where your CI workflow file will live.

  • Create the workflow file by pasting the following into .github/workflows/ci.yml:
name: ci
on:
  push:
    branches:
      - main
permissions:
  contents: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure Git Credentials
        run: |
          git config user.name github-actions[bot]
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
      - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
      - uses: actions/cache@v4
        with:
          key: mkdocs-material-${{ env.cache_id }}
          path: ~/.cache
          restore-keys: |
            mkdocs-material-
      - run: pip install mkdocs-material
      - run: mkdocs gh-deploy --force

What does this workflow do?

  • Triggers on every push to the main branch.
  • Checks out your code, installs Python, and caches dependencies weekly for speed.
  • Runs mkdocs gh-deploy --force which builds your site and pushes the result to a gh-pages branch automatically.
  • The contents: write permission lets the workflow push to gh-pages.
  • Save .github/workflows/ci.yml.
  • Verify the file exists by running:
ls .github/workflows/ci.yml

You should see .github/workflows/ci.yml printed in the output.

Don't see the file?

  • Make sure you created the nested directories with mkdir -p .github/workflows (note the dot before github).
  • Check that you saved the file in the workflows folder, not in the project root.

Still stuck? Help me find my ci.yml file.

✔️ Awesome, I've got everything!

Great. Make sure you've saved .github/workflows/ci.yml and you're ready to push to GitHub.

ⓧ I'd like to double check the full code

name: ci
on:
  push:
    branches:
      - main
permissions:
  contents: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure Git Credentials
        run: |
          git config user.name github-actions[bot]
          git config user.email 41898282+github-actions[bot]@users.noreply.github.com
      - uses: actions/setup-python@v5
        with:
          python-version: 3.x
      - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
      - uses: actions/cache@v4
        with:
          key: mkdocs-material-${{ env.cache_id }}
          path: ~/.cache
          restore-keys: |
            mkdocs-material-
      - run: pip install mkdocs-material
      - run: mkdocs gh-deploy --force
Push to GitHub and watch the workflow run

With your workflow file in place, it's time to push everything to GitHub and let the automation do its job.

  • Commit and push all your files to GitHub by running these commands:
git branch -M main
git remote add origin [[REPO_URL="https://github.com/your-username/wordfreq-docs.git"]]
git add .
git commit -m "Initial commit with documentation"
git push -u origin main

What do these commands do?

  • git branch -M main renames your current branch to main (the workflow triggers on this branch name).
  • git remote add origin connects your local repo to the GitHub remote.
  • git push -u origin main uploads all your files and triggers the workflow.

Find your repo URL on GitHub

Copy the HTTPS URL from the quick setup page you saw after creating the repository. It looks like https://github.com/your-username/wordfreq-docs.git.

After the push completes, your terminal should show a success message with the branch and remote details.

  • Open your repository on GitHub in the browser.
  • Click the Actions tab at the top of the repository page.
  • You should see a workflow run in progress (or already completed) called ci.
  • Wait for the green checkmark to appear. This usually takes 30-60 seconds.

Seeing a red X instead?

  • Click on the failed workflow run to see the error logs.
  • Check that your ci.yml indentation is correct. YAML is whitespace-sensitive.
  • Make sure the file is at .github/workflows/ci.yml (not github/workflows/ci.yml without the leading dot).

Need more help? Help me debug my failing GitHub Actions workflow.

Once you see the green checkmark, the workflow has created a gh-pages branch with your built site. Now you need to tell GitHub Pages to serve from that branch.

  • Click Settings in the repository tab bar.
  • In the left sidebar, under Code and automation, click Pages.
  • Under Build and deployment, next to Source, select Deploy from a branch.
  • In the Branch dropdown, select gh-pages.
  • Leave the folder set to / (root).
  • Click Save.

Why Deploy from a branch?

The mkdocs gh-deploy --force command in your workflow pushes the built HTML to the gh-pages branch. By telling GitHub Pages to deploy from that branch, it serves the pre-built files directly. No extra build step needed on GitHub's side.

Visit your live documentation site

Your documentation is now deploying to the internet. It can take up to a minute for the first deployment to go live after configuring Pages.

  • Before you open the URL, predict what you expect to see. Will it be the same site you saw locally, or will something be different?
  • Open a new browser tab and visit your live site:
https://[[GITHUB_USERNAME="your-github-username"]].github.io/wordfreq-docs/

You should see your full documentation site live on the internet. The home page shows the WordFreq Documentation heading, the navigation sidebar lists all four pages, and the search bar and theme toggle work exactly as they did locally.

  • Click through each page in the sidebar: Home, Tutorial, How-To Guides, Reference.
  • Toggle between light and dark mode using the sun/moon icon.
  • Type a word into the search bar to confirm search is working.

Seeing a 404 page?

  • Check that you selected gh-pages as the branch in Settings > Pages. The dropdown defaults to None if you haven't saved.
  • Wait a full minute and refresh. First deployments can take up to 60 seconds.
  • Verify your Actions workflow has a green checkmark. If it failed, the gh-pages branch was never created.

Still not loading? Help me troubleshoot my GitHub Pages deployment.

Update your site_url

For full functionality (canonical URLs, sitemap, social cards), update the site_url line in mkdocs.yml to match your actual GitHub username. Then commit and push. The workflow will redeploy automatically.

Your documentation site is live on the internet and auto-deploys on every push. Next up, tackle the Secret Mission to add architecture diagrams to your docs.

Secret mission

Add Architecture Diagrams with Mermaid.js

Professional documentation often includes visual diagrams to explain data flow and system architecture. MkDocs Material has built-in Mermaid.js support that renders diagrams from plain text in Markdown. No image editor needed. Can you add flowcharts and sequence diagrams to your docs?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on free resources. Your GitHub Pages site costs nothing to host, and the GitHub repository is free for public repos. There are no ongoing charges to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • GitHub repository (free, public).
  • GitHub Pages site (free, hosted indefinitely).
  • Local project folder on your Desktop.

Keep everything running

No action needed. Choose this if you're still actively building or want your docs site to stay live.

  • Your GitHub Pages site will stay live for free as long as the repository exists.
  • Your local project folder stays on your Desktop for future edits.
  • Any push to main will automatically redeploy your docs via the GitHub Actions workflow.

Pause - I'll come back to this later

No action needed for this project. GitHub Pages has no cost and no running processes to pause.

  • Your site remains live at no cost. There is nothing to shut down.
  • If you want to stop the live site without deleting the repo, go to your repository Settings, select Pages in the left sidebar, and set the source to None. You can re-enable it later by setting the source back to the gh-pages branch.

Delete - I don't want to use this again

Remove all project resources and start fresh. This cannot be undone.

  • Delete the GitHub repository (this also removes the GitHub Pages site):
  • Go to your repository on GitHub.
  • Click Settings in the top navigation bar.
  • Scroll to the bottom of the page to the Danger Zone section.
  • Click Delete this repository and follow the confirmation prompts. You will need to type the full repository name to confirm.

Delete the local project folder from your Desktop. Run the command for your operating system:

macOS

cd ~/Desktop
rm -rf wordfreq-docs

Windows

cd ~\Desktop
rmdir /s /q wordfreq-docs

Nice Work!

Nice Work!

Nice work! You just built a professional documentation site from scratch and deployed it live to the internet. Your docs site has search, navigation, dark/light mode, and auto-deploys every time you push to GitHub.

You've learned how to:

  • Build a documentation site using MkDocs Material and structure content with the Diataxis framework into four distinct documentation types (tutorial, how-to, reference, explanation).
  • Create rich, developer-friendly content using advanced Markdown extensions like admonitions, code annotations, and tabbed content blocks.
  • Set up a CI/CD pipeline with GitHub Actions that auto-deploys your docs to GitHub Pages on every push.
  • Secret Mission: Added Mermaid.js architecture diagrams rendered from plain text directly in your Markdown files.

Ready to quiz yourself?