region = mo.ui.dropdown( ["North", "South"], value="North", label="Region",)regionHow Studio works
Studio joins two things you write: a notebook and a view project. This page uses the view dashboard of analysis.py to show what each part does and which data passes between them.
- You save
index.htmlin Source, in your own editor, or through a coding agent. - Studio copies the build inputs into a snapshot and calls the provider's
build(). - The provider writes the page: an HTML entry file and the files it loads, with empty hosts.
- Preview shows the new page. The session is still running, so its results fill the hosts at once.
The page sits between two sources that change at different times. Saving view source runs a build and publishes a new page. Changing a control reruns notebook cells and sends new results to the page that is already open, with no build. A Typst or LaTeX view adds a third path, in which the provider compiles a new PDF from the new results.
The notebook runs in a session
analysis.py is an ordinary marimo notebook. When someone opens the dashboard, marimo runs the notebook in a session and keeps every cell's results. Three of its cells, abridged:
@app.cell
def region_control(mo):
region = mo.ui.dropdown(
["North", "South"], value="North", label="Region"
)
region
return (region,)
@app.cell
def quarterly_section(mo, orders, plot_quarters, region):
quarterly_chart = plot_quarters(orders, region.value)
mo.vstack([
mo.md("### Orders per quarter"),
quarterly_chart,
mo.md("Source: order log"),
])
return (quarterly_chart,)
@app.cell
def summary(orders, region):
rows = orders[orders["region"] == region.value]
summary = {"orders": len(rows)}
return (summary,)The notebook holds every line of Python the dashboard depends on. Studio reads results from the session and leaves the cells as they are.
The view project holds the presentation
The dashboard's files live in their own folder beside the notebook:
analysis.py
studio/analysis/
dashboard/
view.toml names the provider that builds the view
index.html the page source
AGENTS.md conventions for coding agentsThe view project is an ordinary project in its format, here a plain HTML page. index.html lays out the page and names the notebook results it shows:
<main id="app-shell">
<h1>Regional orders</h1>
<marimo-cell name="region_control"></marimo-cell>
<marimo-output value="quarterly_chart"></marimo-output>
<p>Orders: <strong mo-value="summary.orders"></strong></p>
</main><marimo-cell>, <marimo-output>, and the mo-value attribute are hosts: elements that mark where a notebook result goes. They belong to the page. The notebook never contains them.
Edit the view project in Studio's Source pane, in your own editor, or with a coding agent. Studio watches the folder and builds again when you save a build input, such as index.html. A second view, such as a Typst report, gets its own folder beside dashboard/.
A view provider builds the page
view.toml names the view provider:
schema = 1
provider = "marimo-studio/vanilla"A view provider is Python code that knows one format. For each build, it receives a copy of the view project's build inputs and writes a page: an HTML entry file and the files it loads. The HTML provider copies the page. The React provider type-checks and bundles the app with Deno. The Typst provider prepares a document to compile.
A build never runs the notebook, so the page it writes holds empty hosts. A failed build leaves the last good page in Preview and shows the error beside the source.
Hosts receive results while the page is open
When the page opens, Studio connects it to the session. For each host, Studio resolves the name to the cell that computes it, runs that cell and the cells it depends on, and sends the result to the host. Pick a region in either pane of this figure, or point at a host to find its cell:
quarterly_chart = plot_quarters( orders, region.value)mo.vstack([ mo.md("### Orders per quarter"), quarterly_chart, mo.md("Source: order log"),])Orders per quarter
Source: order log
rows = orders[ orders["region"] == region.value]summary = {"orders": len(rows)}Regional orders
Orders: 534
<main id="app-shell"> <h1>Regional orders</h1> <marimo-cell name="region_control"></marimo-cell> <marimo-output value="quarterly_chart"></marimo-output> <p>Orders: <strong mo-value="summary.orders"></strong></p></main><marimo-cell name="region_control">
Everything the cell shows, as marimo renders it, here the live dropdown. Both panes show one control, so picking a region in either reruns the cells that read it.
| Host | The page receives |
|---|---|
<marimo-cell name="region_control"> | The cell as marimo renders it, with its live dropdown |
<marimo-output value="quarterly_chart"> | The rich representation of the variable quarterly_chart alone, here a figure, or an image of it when the host sets accept |
mo-value="summary.orders" | The value as JSON. Text, a number, or a boolean appears in the element as text, and page code reads any value from its marimoValue property |
A host for an output names a variable, so it can take one piece of what a cell shows. quarterly_section shows a stack with a heading, the chart, and a caption. <marimo-output value="quarterly_chart"> places only the chart, while <marimo-cell name="quarterly_section"> would place the whole stack.
A dataframe reaches page code as a FlechetteTable, which Studio decodes from Arrow IPC. Python objects stay in the session.
When a reader picks South, the browser sends the dropdown's new value to the session. marimo reruns quarterly_section and summary, the cells that read it, and Studio sends their new results to the hosts that show them. Each view reads its results from the notebook, so every view agrees with it. Place notebook results in a view covers each kind of host.
Renders turn results into files
A PDF holds no live elements, so a Typst or LaTeX view reads results while it compiles:
#import "marimo.typ": marimo_output, marimo_value
The #marimo_value("region.value", default: "selected") region placed
#marimo_value("summary.orders", default: 0) orders.
#marimo_output("quarterly_chart", width: 100%)The provider declares this file as a render. When a result the render reads changes, Studio calls the provider with values in their JSON form and outputs as media such as images, and the provider compiles a new PDF with its own tool. The view's page shows the PDF with a download link.
The provider also reports where each result landed in the PDF, as render regions. Studio uses them to trace a figure or a table cell inside the PDF back to the cell that computed it, as Select results with Lens shows.
Where the session runs
The runtime decides where the notebook's Python runs. The view project stays the same for each:
| Runtime | The session runs |
|---|---|
| Python | On a server, through marimo edit or marimo run |
| Browser | In each visitor's browser, through Pyodide |
| Prepared | On your machine, during export, once per prepared state |
A prepared export runs no Python while visitors read it. Studio saved each prepared state's results during export, and the page moves between them. Run or export a view compares the runtimes.
Next steps
- Getting started builds a first view by hand.
- Choose a format compares the built-in formats.
- Select results with Lens traces any part of a view back to its cell.
- Add a view provider connects another framework or tool.
- The glossary defines every term on this page.