# tuitorial: code tutorials that run inside your terminal

> tuitorial is a Python tool for building step by step code tutorials as a terminal application, using Textual for the interface and a set of focus types to point at the exact line, range or pattern of a snippet you want the audience to look at. A tutorial is a Python object or a YAML file, which means it lives in version control and can be regenerated when the code it explains changes.

**basnijholt/tuitorial** — Create beautiful terminal-based code tutorials with syntax highlighting and interactive navigation.

- Repository: https://github.com/basnijholt/tuitorial
- Website: https://tuitorial.readthedocs.io/
- Stars: 526 · Forks: 8
- Language: Python
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/basnijholt-tuitorial

## A TUI for teaching code, with a very specific audience

tuitorial is a tool for creating terminal based code tutorials with syntax highlighting and interactive navigation, and the name is a portmanteau of TUI, terminal user interface, and tutorial. The user interface is powered by Textual, and the project is published on PyPI, documented at tuitorial.readthedocs.io, and maintained by Bas Nijholt under the MIT licence.

The positioning is unusually blunt for a project README. It says that 99.9 percent of people should not use tuitorial, and that the 0.1 percent who should will love it. The argument that follows is about slide decks: the project wants you to ditch PowerPoint, Google Slides and Beamer and embrace the terminal, on the grounds that code should be the focus rather than a picture of code, that a step by step walkthrough is more engaging than a static page, and that a tutorial defined in code is reproducible and versionable so it can be tracked and updated. It also notes that it is parameterisable, so a tutorial can be driven by code snippets and focus points rather than being a fixed transcript.

Those are the real differentiators. A tutorial in tuitorial is a Python object or a YAML file, which means it is a source artefact, and a workshop can build the tutorial from the same code it is demonstrating rather than pasting in a stale copy.

The project is classified as Development Status :: 4 - Beta, requires Python 3.10 or newer, and its release history shows three versions in the past year: v0.15.0 on 2025-10-01, v0.15.1 on 2026-01-20 and v0.16.0 on 2026-01-22, with the last push on 2026-10-19.

## Focus types, the part that does the real work

The feature that makes tuitorial more than a code viewer is the set of focus types, and there are eight of them: literal match, regular expression, line number, range, starts with, between, line containing, and syntax highlighting.

The idea is that a step in a tutorial points at a piece of the code and the tool highlights it while everything else stays dimmed. The difference between the focus types is how precisely you can say what you mean.

A literal match is the simplest, and it is the most fragile, because the highlighted text has to appear exactly. A regular expression is more tolerant of formatting changes while still being specific about content. A line number is the most fragile of all, because it moves whenever the file changes, and a range is the same idea extended over a span, which is how you would highlight a whole function. Starts with and between are intermediate options, and line containing combines the tolerance of a regex with the readability of pointing at a phrase.

Syntax highlighting as a focus type is the interesting one, because it does not point at a location in the source at all. It points at a construct, so the highlight follows the code wherever it is.

The Python API expresses all of this with a Focus class and a style argument, so the highlight is a Rich style rather than a colour name. A step takes a description and a list of focus objects, and the example in the README uses a regex with a bold yellow style for a function definition, a literal with a bold green style for a return statement and a range with a bold blue style for the main function. Because a step holds a list, a step can carry several highlights, which is how you point at two related pieces of code in one breath.

## A tutorial in YAML, and a live reload while you edit it

The installation is one command, and the quick start suggests running an existing tutorial without installing anything at all if you have uv installed:

```bash
pip install tuitorial
```

The YAML form is a chapters list, and each chapter has a title, a block of code and a list of steps, and each step has a description and a focus list where the focus entries carry a type, the pattern or text or range, and a style:

```yaml
chapters:
  - title: "Basic Example"
    code: |
      def hello(name: str) -> str:
          return f"Hello, {name}!"

      def main():
          print(hello("World"))
    steps:
      - description: "Function Definition"
        focus:
          - type: regex
            pattern: "def hello.*:$"
            style: "bold yellow"
```

Running it is a single command with the watch flag:

```bash
tuitorial --watch tuitorial.yaml
```

The watch flag is the feature that makes this practical for the author of a tutorial. The project describes live reloading as automatically refreshing the app on a YAML update, and the dependency list has watchfiles for exactly that. So you edit the YAML file in one window and the tutorial updates in the terminal, which is the difference between authoring a tutorial and re-running a script to see what changed.

There is a Python entry point as well, described in the README as a provided run_from_yaml function, so the same file can be launched from a Python program. The Python form is the same data in a different shape, with a Chapter, a list of Step objects, a list of Focus objects and an app object that is run:

```python
from tuitorial import Chapter, Step, TuitorialApp, Focus
from rich.style import Style

code = '''
def hello(name: str) -> str:
    return f"Hello, {name}!"

def main():
    print(hello("World"))
```

```python
steps = [
    Step(
        "Function Definition",
        [Focus.regex(r"def hello.*:$", style="bold yellow")]
    ),
]
chapter = Chapter("Basic Example", code, steps)
app = TuitorialApp([chapter])
app.run()
```

The YAML and the Python forms are the same tutorial, and the examples directory in the repository ships both, so you can see the translation rather than infer it.

## The other half of the feature set: markdown, images and title slides

The remaining features are what a tutorial needs around the code itself.

Markdown rendering is supported, and there is a dedicated example file for markdown descriptions, so a step's description can be formatted rather than being a single line of text. Images can be embedded, with an ImageStep type, and the documentation covers image positioning and sizing, along with alignment, so a screenshot can be placed next to the snippet it explains.

Title slides are the other idea that stands out. The feature list mentions an eye catching ASCII art title slide, and the dependency list explains how: pyfiglet is the library that renders text as ASCII art, and it is a small dependency with a large effect on how a session opens. chardet is there to detect character encodings, which matters when a tutorial embeds a file with an unexpected encoding.

The navigation model is keyboard and scroll based, and the documentation has a controls section listing them. The project also describes a helper function for building a bullet point chapter, which is the shape you want when the content is a list of points rather than a piece of code, so a chapter is not necessarily about code even though code is the default case.

Custom highlighting patterns and multiple highlights per step are both listed as features, and the styling section covers how the colours and styles are customised, which together mean the look is yours to set rather than fixed.

## A container that serves the same app as a web app

The repository contains a webapp/ directory and a Dockerfile, which is an unusual combination for a terminal tool and worth understanding.

The image is built from the uv python 3.13 bookworm slim base, and it does the sensible container things: it creates a system group and a system user, copies the source with the right ownership, switches to that unprivileged user, and syncs the environment with uv. The command it runs is a panel server:

```dockerfile
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
WORKDIR /app
USER appuser
EXPOSE 80
ENV APP_ENV=TUITORIAL_DOCKER_WEBAPP
```

The environment variable marks the app as running in its Docker web app mode, and the final command serves a Panel application from webapp/app.py on port 7860, bound to all interfaces, with websocket origins allowed. The Dockerfile serves this Panel application from webapp/app.py, and the websocket allowance is what lets a browser client connect to it.

For a terminal tool, that is a meaningful addition, because it means a tutorial can be presented on screen at a conference or shared as a link, without the presenter having a terminal on stage.

The justfile at the top level keeps the workflows short. It defines aliases, t for test and x for example, and then the test target syncs the environment and runs pytest, the example target runs the pipefunc tutorial with the watch flag, and the webapp target serves the Panel application with autoreload. The Dockerfile is built on the same uv based environment, and the repository also has a.devcontainer directory for development in a container.

## Dependencies, packaging and what to check before adopting it

The runtime dependencies are a short list with a reason behind each. textual is the user interface framework, rich provides the styling and the console rendering, pyyaml parses the YAML tutorial format, pyfiglet draws the ASCII art title, textual-image handles the image steps, watchfiles provides the live reload, and chardet handles encodings.

The packaging is hatchling with versioningit, and the version is dynamic, so it is derived from the repository tags rather than written in the metadata. The project is typed, with a Typing :: Typed classifier, and the documentation is built with Sphinx and myst-nb, which is the combination that lets a documentation page execute the code in it, which is the same idea as the tutorial itself.

The development group is thorough: pytest with coverage, pytest-asyncio, pytest-timeout, IPython and a kernel for running notebooks, and the Sphinx extensions. The examples directory holds three tutorials, the pipefunc one that the recording in the README shows, one with markdown descriptions and one basic one, so there is something to read before you write your own.

Two things are worth checking before you adopt it. The first is the release rhythm, which is uneven: a gap of three and a half months between v0.15.0 and v0.15.1, then v0.16.0 two days later, with the last push more recent than any of them. The project is in beta and being worked on, so pin a version rather than tracking the branch.

The second is the fragility of your focus points, and that is a design decision rather than a bug. A literal match or a line number breaks when the code moves, and a tutorial that highlights the wrong line in a workshop is worse than one that highlights nothing. The README's own YAML example uses a regex for the function definition and a range with hard calculated indices for the main function, and the comment on those indices, which calls it a calculated index for the main function, shows the author doing the arithmetic by hand. Use the most tolerant focus type that still says what you mean, and regenerate the tutorial when the code changes.

## Conclusion

tuitorial fits a developer who teaches code in a terminal, in a workshop, in a screencast or in documentation that should stay close to the source, because a tutorial defined in Python or YAML can be regenerated, diffed and reviewed, and the focus types let you point at exactly the lines that matter. It is the wrong tool if your audience is not looking at a terminal, since the whole interface is a curses style TUI, and it is the wrong tool if you need slides rather than a live program. Before you commit, try the one line uvx example the README suggests, because it runs an existing tutorial without installing anything, and read the focus types list, since the choice between a literal match, a regex and a line range changes how fragile your tutorial is when the underlying code moves.

## FAQ

### What is tuitorial and who is it for?

tuitorial is a Python tool for creating terminal based code tutorials with syntax highlighting and interactive navigation, built on the Textual framework. The README says plainly that 99.9 percent of people should not use it, and that its purpose is to replace slide decks with a walkthrough focused on the code itself, defined in Python or YAML so it is reproducible and versionable.

### How do I install and run a tuitorial tutorial?

Install it with pip install tuitorial, then run a tutorial with tuitorial --watch tuitorial.yaml, or use the provided run_from_yaml function from Python. The watch flag reloads the app when the YAML file changes, and the README also suggests running an existing example in one line with uvx if you have uv installed.

### What focus types does tuitorial support?

There are eight: literal match, regular expression, line number, range, starts with, between, line containing, and syntax highlighting. A step can carry several highlights at once, and each focus entry names a type, the pattern or text or range, and a Rich style such as bold yellow.

### Can a tuitorial tutorial be presented in a browser?

The repository includes a webapp directory and a Dockerfile that serves a Panel application from webapp/app.py, so the same content can be presented on screen or shared as a link rather than only in a terminal. The container runs as an unprivileged user and marks itself with the APP_ENV=TUITORIAL_DOCKER_WEBAPP variable.

## Sources

- [basnijholt/tuitorial on GitHub](https://github.com/basnijholt/tuitorial)
- [License: MIT](https://github.com/basnijholt/tuitorial/blob/main/LICENSE)
- [Project website](https://tuitorial.readthedocs.io/)
- [README](https://github.com/basnijholt/tuitorial/blob/main/README.md)
- [Releases](https://github.com/basnijholt/tuitorial/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/basnijholt-tuitorial
