This page is itself a scrollytelling page. Each card explains one feature and shows its syntax, and the stage next to it shows that feature working. W2D1 Tutorial 2 is a full tutorial built this way.
neuromatch course kit
Tutorial 7: Scrollytelling pages¶
A pinned stage of figures, videos and live Python, with text cards that scroll past.
Time: about 20 minutes
A scrolly page¶
Is still one flat MyST
.mdfileStill produces a normal notebook for Colab and Kaggle
Shows real figures before Python starts
Runs Python in the browser when the reader asks
Step options¶
hero: full screen, no card:section:label in the progress bar:kicker:label above the card{stage}for stage-only markdown
# The demo from the last step, as it appears in the notebook
# @title Sliders and dropdowns
import ipywidgets as widgets
@widgets.interact(freq=widgets.FloatSlider(value=2.0, min=0.5, max=5, step=0.5, continuous_update=False), shape=widgets.Dropdown(options=['sine', 'square', 'sawtooth'], value='sine'))
def _demo(freq, shape):
t = np.linspace(0, 2, 1000)
...Video 1: Linear Dynamical Systems
Video available at https://
Video available at https://
# myst.yml
extend: https://cdn.jsdelivr.net/npm/@neuromatch/course-kit@0.1/dist/nma.yml # the course kit's MyST plugin
nma:
scrolly:
pyodide_url: https://cdn.jsdelivr.net/pyodide/v314.0.7/full/# Preview with real outputs locally
uv run nma-kit render-scrolly
uv run myst start
# Writes _build/scrolly/<Day>/<Tutorial>.jsonthat's it
Now write your own¶
Start from W2D1 Tutorial 2, or add layout: scrolly and a few {step} markers to a page you already have.
What and when
What a scrolly page is¶
A scrolly page is an ordinary tutorial .md file with one extra frontmatter line. The page is split into steps. Each step has a card (the text you are reading) and a stage (the pinned area next to it, or above it on a phone).
Use it for narrative walkthroughs: a sequence of figures, short demos and videos, where each idea needs one picture.
Don’t use it for long reference material, or for pages that are mostly heavy exercises. Those read better as normal pages.
Turning it on
layout: scrolly and {step}¶
Add layout: scrolly to the frontmatter, hide the “On this page” outline (recommended, see below), then put a {step} marker wherever a new step starts. A step runs from its marker to the next one. Markers have no body: the content goes after the closing fence.
---
title: "Tutorial 2: My topic"
layout: scrolly
site:
hide_outline: true
kernelspec:
name: python3
display_name: Python 3
---
:::{step} hero
:::
# My topic
:::{step}
:::
Text for the first card.
:::{step}
:::
Text for the second card.MyST logs extra key ignored: layout, which is expected.
hide_outline removes the “On this page” outline on this page only, which gives the scrolly section more room. Keep the table of contents (don’t set hide_toc): on a page this long it is how readers move to other pages without scrolling to the end. The section fits between the menus, and when that is too narrow the stage sits above the cards instead of beside them. The previous/next links at the bottom of the page work as on any other page.
Options
hero, :section:, :kicker: and {stage}¶
{step} heromakes a full-screen title or closing step, with no card. The first step of this page is one.:section:sets the label in the progress bar at the top of the stage. It carries forward until the next step that sets it.:kicker:is the small label above a card, like “Options” above this one.{stage}holds markdown that only appears on the stage.:style: listturns a list into the numbered chips shown here.:class:adds CSS classes.
:::{step}
:section: Basics
:kicker: Options
:::
Card text.
:::{stage}
:style: list
1. Shown on the stage as a chip
2. Another chip
:::Stage or card
What goes where¶
The plugin sorts each step’s content for you:
| Stage | Card |
|---|---|
code cells (except remove-cell) | prose, math, headings |
images and {figure} (also .mp4) | {nma-solution} / {nma-explanation} links |
{nma-video}, {nma-slides} | {nma-feedback} |
the {stage} block | everything else |
If a step has nothing for the stage, the previous stage stays. This step is an example: the chips from the last step are still showing. Content before the first {step} renders as a normal page header.
Code cells
An editable cell¶
An untagged code cell becomes an editable panel with Run and Reset. Change the code and run it (Ctrl+Enter also works).
The first Run downloads Python into your browser. All cells on the page share one namespace, and running a cell first runs any earlier cells that haven’t run yet. So rng below comes from the hidden setup cell at the top of the page, without you running it.
Code cells
Tags choose how a cell looks¶
| Tag | On the stage |
|---|---|
| none | editable panel (code + output) |
hide-input | code collapsed behind “Show code” |
remove-input | output only |
remove-cell | invisible, runs before the first visible cell |
This step’s cell is tagged hide-input: the output shows, and the code is one click away.
Code cells
Output-only figures¶
A remove-input cell shows only its output. Use it for figures that tell the story, where the code would be a distraction.
```{code-cell} python
:tags: [remove-input]
plt.plot(...)
plt.show()
```Interactive demos
Sliders and dropdowns with # @param¶
Mark an assignment with a Colab form comment, and the panel gets a control. Moving it reruns the cell.
freq = 2.0 # @param {type:"slider", min:0.5, max:5, step:0.5}
shape = "sine" # @param ["sine", "square", "sawtooth"]Rules: one top-level assignment per line, numbers for sliders (step defaults to 1), and a list of literals for dropdowns that includes the default. Before Python starts, the stage shows the output for the defaults.
Interactive demos
From widgets.interact to # @param¶
Existing NMA demos use ipywidgets. On a scrolly page, rewrite them with # @param:
# Before
@widgets.interact(a=widgets.FloatSlider(value=-0.5, min=-2.5, max=1.5, step=0.25))
def plot_demo(a):
plt.plot(t, np.exp(a * t)); plt.show()
# After
a = -0.5 # @param {type:"slider", min:-2.5, max:1.5, step:0.25}
plt.plot(t, np.exp(a * t)); plt.show()You don’t lose anything in the notebooks: the generated .ipynb turns every # @param cell back into a widgets.interact demo, as shown on the stage.
Exercises
Exercises, solutions and explanations¶
These work exactly as in Tutorial 5: an untagged exercise cell, directly followed by an {nma-solution}. The card gets the Click for solution link.
Try it: fill in cube, or don’t. If you run the next cell first, the page runs the reference solution in place of the unfinished exercise and says so, so you can always run ahead.
Exercises
Running ahead¶
This cell uses cube from the exercise. Run it before finishing the exercise to see the fallback note.
Media and feedback
Videos and feedback¶
{nma-video} and {nma-slides} go on the stage, and {nma-feedback} stays on the card. Both work as in Tutorial 4. Media keep playing only while their step is active: muted .mp4 figures play and pause as you scroll.
Did this step make sense?
Browser Python
Python in the browser¶
The Enable Python pill at the top of the stage (or any Run button) starts Pyodide once for the page. The first load is about 10 MB plus the packages a cell imports, and the browser then caches it. JupyterLite is not used on scrolly pages, so its power button is hidden.
Packages come from the cells’ imports. numpy, matplotlib, scipy and pandas all work. Anything Pyodide can’t run belongs in the Colab notebook.
No sockets: load data files with
pyodide.http.open_urlundersys.platform == "emscripten"(see the setup cell of W2D1 Tutorial 2).The Pyodide build comes from
nma.scrolly.pyodide_urlinmyst.yml. The host must send CORS headers.
Pre-rendered outputs
Real figures before Python starts¶
In CI, nma-kit render-scrolly runs every scrolly page’s instructor notebook (solutions filled in, # @param at their defaults) and saves the outputs. The plugin puts them in the page, so every figure above appeared without starting Python. Never type outputs by hand.
A cell that raises fails the build, naming the file and line.
Exercise cells get no starting output.
Without a render file (for example, plain
myst start), panels show Run to see output.
Notebooks
The Colab and Kaggle version¶
A scrolly page with a kernelspec still gets a linear notebook, generated the usual way:
{step}markers disappear. A:kicker:becomes an italic line.{stage}blocks become plain markdown.# @paramcells becomewidgets.interactdemos.Videos, solutions and feedback become the usual NMA cells, with student and instructor versions.
Open this page’s notebook from the badges at the top to see the result. A scrolly page without code or kernelspec is site-only.
Converting a tutorial
Converting an existing tutorial¶
W2D1 Tutorial 2 is Tutorial 1 converted this way:
Copy the page and add
layout: scrolly, plussite: hide_outline: true.Merge setup, figure style and helpers into one
remove-cellcell. Drop%magics and the ipywidgets import.Add a
{step}before each idea that needs its own picture, with:section:at section starts and:kicker:labels.Rewrite
widgets.interactdemos with# @param.Add
remove-inputfigures where a card has nothing to show.Avoid “on the right” in card text: the notebook reads it too.
Run
--renderandmyst start, and scroll through it.
CI errors
Errors you may see¶
| Message | Fix |
|---|---|
{step} only works on pages with layout: scrolly | Add layout: scrolly, or remove the marker |
page has layout: scrolly but no {step} markers | Add at least one {step} |
{step} has no body | Move the content below the closing ::: |
{step} takes no argument or hero | Use {step} or {step} hero |
unknown option | {step}: :section:, :kicker:. {stage}: :style:, :class: |
step has more than one {stage} / before the first {step} | One {stage} per step, after its marker |
must be at the top level | Don’t nest markers in other directives |
# @param must not be indented / must be an assignment | One top-level name = value # @param ... per line |
default ... is not one of the options | Include the default in the dropdown list |
T.md:42: ZeroDivisionError (render) | Fix the cell at that line; it raised in CI |