Notebook result projections
A host places one notebook result in a view. Studio resolves the host to one producer cell, runs its dependency closure in the selected notebook runtime, and mounts the result in the host element.
| Host | Target | Browser result |
|---|---|---|
<marimo-cell name="summary"> | Named cell or Studio cell alias | Complete native cell presentation |
<marimo-output value="chart"> | Notebook value selector | One value rendered by marimo's output renderer |
mo-value="metrics.total" | Notebook value selector | JSON value or Arrow-backed table exposed to browser code |
Pages use the hosts above, which Quarto's marimo shortcode also writes. A render reads the same cells, outputs, and values on the server: Typst reads targets with marimo_value(), marimo_output(), and marimo_cell(), and LaTeX with \marimovalue, \marimographics, and \marimocell. A page shows a render's files with <marimo-output render="...">, as Rendered files describes.
The Python (server) and Browser (wasm) runtimes use the same authored hosts. In HTML, close each host explicitly and place hosts beside each other. Rendering a host replaces its contents. The build rejects nested hosts and self-closing elements such as <h1 mo-value="total"/>.
<h1 mo-value="total"></h1>
<marimo-cell name="summary"></marimo-cell>
<marimo-output value="chart"></marimo-output>Complete cells
<marimo-cell name="summary"></marimo-cell>name accepts a native marimo cell name or an alias under [tool.marimo-studio.cells]. The host receives the cell's native output, controls, console output when show_cell_logs is enabled, error state, and reactive updates.
A presentation mounts each cell target once. A second host for the same target enters data-state="error" with code duplicate-cell-host.
Rendered outputs
<marimo-output value="chart"></marimo-output>value selects a Python value, so assign the figure or object to a notebook variable, such as chart = plot_revenue(rows). marimo renders it through its native output renderer and updates it when its producer reruns.
accept shows the value as an image instead. List image types in order of preference, separated by spaces or commas:
<marimo-output value="chart" accept="image/svg+xml image/png"></marimo-output>Studio renders the value in the first listed type it supports through marimo-export's represent(). The notebook's output settings stay unchanged, so the notebook can show a PNG while the page shows an SVG.
- A page host accepts
image/svg+xml,image/png,image/jpeg, andimage/gif. - A matplotlib figure or axes renders as SVG or PNG. A PNG displays at the size marimo shows the figure with twice the pixels, for high-density screens.
- An Altair chart renders as SVG or PNG with
vl-convert-python. The Browser runtime lacksvl-convert-python, so leaveacceptoff there to show the interactive chart. Select the chart itself, not amo.ui.altair_chartwrapper. - Other values use their display methods.
- A figure keeps the style it was drawn with, including marimo's dark-theme style.
A value without an accepted type puts the host in data-state="error" with code output-media-unavailable. Studio reads accept at build time, so later script changes to the attribute have no effect.
A view reads each output target in one form. Every literal host and every document that reads a target lists the same media types, and a different list reports output-accept-conflict at its source location. A host with data-marimo-allow="*" shows each target in the form its literal hosts declare, and an accept on that host reports projection-accept-invalid.
A presentation mounts each output target once. A second host for the same target enters data-state="error" with code duplicate-output-host.
Browser values
<strong mo-value="metrics.total"></strong>Studio writes a scalar value into the element's text content, null as an empty string, and arrays and objects as compact JSON. The host's non-enumerable, read-only marimoValue property holds the decoded value:
const host = document.querySelector('[mo-value="metrics.total"]');
host.addEventListener("marimo-value-updated", (event) => {
console.log(event.detail.value);
console.log(host.marimoValue);
});Several mo-value hosts may select the same target. Each receives its own update event and reads the current decoded value.
Value selectors
A value selector starts with a notebook variable and may contain these path steps:
metrics.total
rows[0]
lookup["north-region"]| Syntax | Resolution |
|---|---|
name | Notebook variable |
.field | Mapping key when present, then Python attribute |
[0] | Non-negative item index |
["key"] | Item access with a JSON string key |
The root uses Python identifier syntax. Dot selection rejects names that start with _. Bracket indexes must be JavaScript safe integers. A selector has at most 64 path steps and 4,096 UTF-8 bytes. Studio parses selectors with marimo-export's ValueSelector, so a view and a Prepared export accept the same selectors. Limits lists the complete projection budget.
Value codecs
| Codec | JavaScript value | Contract |
|---|---|---|
json-v1 | null, boolean, number, string, array, or object | marimoValue returns the decoded JSON-compatible value |
arrow-ipc-v1 | Flechette Table | marimoValue returns the table with its source bytes and fingerprint attached |
A json-v1 value encodes within 1,000,000 bytes and an arrow-ipc-v1 value within 64 MiB. Runtime payloads lists the per-read budgets.
Studio decodes Arrow tables with Flechette's default extraction options. Integer columns, including 64-bit integers, read as numbers, and a 64-bit value beyond Number.MAX_SAFE_INTEGER throws. Booleans and strings keep their JavaScript types, list cells read as arrays, and dates and timestamps read as epoch milliseconds. table.toArray() returns plain row objects.
React starters export useMarimoValue() and getMarimoDataSource() from src/lib/use-marimo-value.ts, and Svelte starters export observeMarimoValue() and getMarimoDataSource() from src/lib/marimo-value.ts. getMarimoDataSource(table) returns the codec, fingerprint, and immutable Arrow IPC bytes of an Arrow-backed table. Arrow IPC is a columnar data interchange format.
Host lifecycle
Studio records the current lifecycle phase in data-state.
| State | Meaning | aria-busy |
|---|---|---|
connecting | The host is waiting for projection resolution or a runtime connection | true |
loading | The producer or selected result is pending | true |
stale | The host retains a previous result while a current result is pending | true for output and value hosts |
ready | The host displays the current result | Removed |
missing | A complete-cell target has no current result | Removed |
error | Resolution, execution, rendering, duplication, or decoding failed | Removed |
Value hosts keep their previous marimoValue while entering loading or stale. A terminal value error clears the property and dispatches marimo-value-error.
Cell events
| Event | Dispatch | Detail fields |
|---|---|---|
marimo-cell-ready | First transition to ready | alias, runtimeId, outputMime, or diagnostic fields when present |
marimo-cell-updated | A later transition to ready | Same shape as marimo-cell-ready |
marimo-cell-error | First transition into the current error state | alias, runtimeId, code, message, hint when present |
Output events
Value hosts, <marimo-output value>, dispatch these details. Render hosts dispatch the same event names with a render record.
| Event | Dispatch | Detail fields |
|---|---|---|
marimo-output-ready | First transition to ready | selector, cellId, mimetype, or diagnostic fields when present |
marimo-output-updated | A later transition to ready | Same shape as marimo-output-ready |
marimo-output-error | First transition into the current error state | selector, cellId, code, message, hint when present |
Value events
| Event | Detail |
|---|---|
marimo-value-updated | { selector, value } |
marimo-value-error | { selector, code, message, hint? } |
Host events bubble and cross shadow DOM boundaries. Shadow DOM is the browser boundary that encapsulates a component's internal document tree. Listen on the host for one projection or on document for the presentation.
Authored sites and dynamic targets
A view provider reports each authored host as a projection site. Studio adds data-marimo-studio-site to the host in the build snapshot, and the attribute carries the site into the built page. Leave that attribute to Studio.
A site with a finite target set can request those targets. A site with targets "*" permits a dynamic target of the declared kind. The built-in React and Svelte providers infer finite targets from literals and bounded constant expressions. Add data-marimo-allow="*" when a framework expression selects its target at runtime:
<marimo-cell name={selectedName} data-marimo-allow="*" />The HTML provider authorizes each host for its authored target. Add the same wildcard when page JavaScript changes the host's selector:
<span id="details" hidden mo-value="details.first" data-marimo-allow="*"></span>An unbounded expression without the literal wildcard produces projection-target-unbounded, and any other data-marimo-allow value produces projection-wildcard-invalid. Static validation checks finite targets, and browser assertions verify the rendered results of a dynamic site.
Changing name, value, or mo-value releases the prior target and resolves the same DOM instance against the new one. Removing the host releases its projection ownership.
Rendered files
A render host shows the files a view provider renders from notebook results, such as the PDF the Typst or LaTeX provider compiles or a video a scene provider encodes:
<marimo-output render="main.typ"></marimo-output>render names the project file the render starts from, as Source shows its path. A host for a file the view does not render enters error with code render-unknown, and the build fails with the same code when the built page's HTML holds one. A host with both render and value enters error with code render-host-invalid. A render host is part of the page and needs no projection site.
Studio renders again whenever a result the render reads changes. The first change to those results during a render cancels it, and later changes wait for the replacement to finish, so results that change faster than renders finish still show. The host keeps the last files on screen while new ones render, and shows render errors beside them. It shows the main file by its media type:
| Main file | The host shows |
|---|---|
application/pdf | The pages, with selectable text |
image/* | The image |
video/* | A video player with seeking, and the preview image as its poster |
audio/* | An audio player |
text/html | The page in a sandboxed frame with no access to the view |
| Another media type | The preview when it has one of the types above, otherwise a note |
Every host also shows a Download link for the main file. Studio serves rendered files with byte ranges, so media seeks without loading the whole file. Style the host's parts from page CSS with ::part(toolbar), ::part(download), ::part(problems), and ::part(content).
Present files yourself
Markup inside a render host replaces the stock presentation, and the page fills it from the host's render record. The toolbar and problems stay. A script that runs after the first files are ready starts from host.marimoRender:
<marimo-output render="scene.py">
<video id="player" controls loop></video>
</marimo-output>
<script>
const host = document.querySelector('marimo-output[render="scene.py"]');
const player = document.querySelector("#player");
const show = (record) => {
const file = (path) => record.files.find((item) => item.path === path)?.url;
player.src = file(record.main);
player.poster = file(record.preview) ?? "";
};
if (host.marimoRender) show(host.marimoRender);
host.addEventListener("marimo-output-ready", ({ detail }) => show(detail));
host.addEventListener("marimo-output-updated", ({ detail }) => show(detail));
</script>Render records
host.marimoRender holds the files a render host shows, and the marimo-output-ready and marimo-output-updated events carry the same record as detail.
| Field | Value |
|---|---|
render | The render's source file |
bundle | The content identity of the files |
main | Path of the main file |
preview | Path of the preview file, or null |
files | { path, mediaType, size, url } for every file |
warnings | Render warnings as diagnostics |
absent | Targets the render read without a current result, shown as default |
regions | Parts of the files and the results they show, as RenderRegion |
marimo-output-ready fires for the first files, marimo-output-updated when the files change, and marimo-output-error on the first transition into error, with { render, code, message, hint, diagnostics }. Studio serves rendered HTML, SVG, and XML files with a sandbox policy, so such a file opened on its own runs in an opaque origin. A static export's web server sets its own headers.
Select parts of rendered files
A render's regions make the parts of its files selectable with Lens. Select a chart inside a Typst PDF, a table cell, or a shape in a paused video, and Lens records the cells that produced it, the value selector it shows, such as report.readings[6].occupancy, and the source line that reads it, such as main.typ:85. Selecting the file outside its regions records the results the render reads, up to 64 of them.
Studio places a box over each region, in the host's light DOM, on these surfaces:
| Surface | Regions shown |
|---|---|
| A PDF page the host shows | The regions of that page |
| An image or video the host shows | The regions of the file |
An img or video in the page's own markup | The regions of the bundle file its src names |
An element with data-marimo-render-file | The regions of that file, on data-marimo-render-page for a PDF |
A video's regions follow its current time, and before it plays, its poster shows the regions of the preview file. While Lens selects, Studio outlines each region with a dashed line. Region boxes ignore the pointer otherwise, so readers scroll, select PDF text, and use video controls as usual. A page that draws a file itself, such as onto a canvas, marks the element that shows it:
<marimo-output render="main.typ">
<canvas data-marimo-render-file="main.pdf" data-marimo-render-page="1"></canvas>
</marimo-output>Each box names its file part in data-marimo-lens-media, such as {"path":"main.pdf","page":2} or {"path":"animation.mp4","start":3.2,"end":4}, and Lens records it as the selection's description.media.
The preview runs in an opaque origin, so Lens captures no snapshot image of a video frame and names that reason on the selection. The selection keeps its notebook sources, label, and source line.
Render values and runtimes
#import "marimo.typ": marimo_output, marimo_value
= Occupancy
Rooms in use: #marimo_value("summary.rooms", default: 0)
#marimo_output("occupancy_chart", width: 100%)Values arrive in their JSON form: a dataframe as a list of row objects and a date as ISO 8601 text. A value the notebook has not computed renders with the template's default. A value without a JSON form fails with render-value-not-json, and a value too large to read fails with render-values-too-large. An output arrives in the first media type the render accepts and the value supports, such as PDF for a matplotlib figure in a Typst report, drawn at the size the build measured at its placement. A cell read receives the cell's output as marimo shows it, in the first accepted type it carries, such as PNG for a matplotlib figure. An output or cell without an accepted type, such as text or a table, renders with the template's default, and the host notes Showing the default for <target>. A zero-python export renders every output in every prepared state and stops when one state's value has no accepted type.
A render reads its targets on its own, so a page can show a chart natively while a render places the same chart as a PDF.
Each runtime supplies results from a different place:
| Runtime | Results come from | Files |
|---|---|---|
| Python, edit and run mode | The reader's kernel session, read by Studio | Rendered by Studio when a result changes |
| Browser, edit mode | The notebook state in the editor tab | Rendered by Studio when a result changes |
| Prepared, edit mode | The prepared state the preview shows | Rendered by Studio for each state change |
Static export, zero-python | Each prepared notebook state | One bundle per state, rendered at export |
| Browser, run mode | Unavailable | render-results-unavailable error |
Static export, wasm | Unavailable | render-results-unavailable error |
In every runtime, a render reads its results between two notebook runs, so one render never mixes results from different runs. A Prepared state holds one run by construction. For published views, Studio renders from session values and refuses values posted in run mode with render-values-unverified.
Presentation readiness
Wait for html[data-marimo-studio-state="ready"] before asserting the application's result. This state covers Studio's runtime and mounted projections. Custom charts, frameworks, and remote requests need their own assertions. data-marimo-studio-revision on <html> holds the committed presentation revision.
An uncaught error or unhandled rejection from a view file or inline module script sets the state to error for the rest of that document. Studio shows the first one with its built file and position, such as main.js:2:23: TypeError: <message> or inline script:2:23: ..., and marimoStudio.diagnostics() reports it with code view-script-error. Errors from Studio's runtime, notebook files, and remote scripts, and browser warnings such as ResizeObserver loop notices, leave the state unchanged.
A failed build leaves the state as it was and keeps the previous build on screen. The page shows an alert that starts with Latest build failed, and marimoStudio.diagnostics() lists the build's first error, such as view-build-failed.
Host state is the lifecycle contract for one mounted result. marimo-studio:runtime-ready fires on document when the notebook runtime reaches its ready boundary, and marimo-studio:idle fires when the presentation settles. Inspect the current DOM state when attaching after those events.
Browser automation locates connected cell, output, and value hosts with STUDIO_RESULT_SELECTOR.
Lens attributes
Select results with Lens shows these attributes in use. Studio marks hosts and render regions itself. A page sets the others on the elements it authors:
| Attribute | On | Value and effect |
|---|---|---|
data-marimo-lens-inputs | An element that page code draws into | Host IDs in the same document, separated by spaces. Declares the element's complete notebook inputs. Missing, duplicate, or unbound IDs make the element unavailable, and references cannot chain through another annotated element |
data-marimo-lens-label | A host or an annotated element | Plain text that replaces the label Lens shows while selecting. The selection's notebook cells stay the same |
data-marimo-lens-detail | A host or an annotated element | Plain text shown under the label, such as the row a selection names |
data-marimo-lens-scope | #app-shell | A CSS selector list. Lens groups a click on authored content into the nearest match. Studio sets a default when the page leaves it out |
data-marimo-lens-target | An authored element with a stable id | Makes the element a target of its own, so Lens reconnects feedback to it across rebuilds |
data-marimo-lens-render-source | An authored target | JSON such as {"path":"index.html"} naming the source file that holds the element |
data-marimo-lens-context | A chart, card, or section | Marks the element that frames the selection image when Lens selects something inside it. Studio sets it on render region boxes |
STUDIO_RESULT_SELECTOR, from the Python API, matches hosts, elements with data-marimo-lens-inputs, the parents of hidden value hosts, and elements with data-marimo-lens-target. Pass it to Lens(dom_selector=...) for a Lens that a view places.
A selector computed from page state needs the Python or Browser runtime, because a Prepared export needs a finite set of authored targets. Lens keeps value selectors and producing cells. It follows no JavaScript dataflow and pins no past notebook value to captured pixels.