Skip to content

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.

HostTargetBrowser result
<marimo-cell name="summary">Named cell or Studio cell aliasComplete native cell presentation
<marimo-output value="chart">Notebook value selectorOne value rendered by marimo's output renderer
mo-value="metrics.total"Notebook value selectorJSON 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"/>.

html
<h1 mo-value="total"></h1>
<marimo-cell name="summary"></marimo-cell>
<marimo-output value="chart"></marimo-output>

Complete cells ​

html
<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 ​

html
<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:

html
<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, and image/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 lacks vl-convert-python, so leave accept off there to show the interactive chart. Select the chart itself, not a mo.ui.altair_chart wrapper.
  • 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 ​

html
<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:

js
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:

text
metrics.total
rows[0]
lookup["north-region"]
SyntaxResolution
nameNotebook variable
.fieldMapping 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 ​

CodecJavaScript valueContract
json-v1null, boolean, number, string, array, or objectmarimoValue returns the decoded JSON-compatible value
arrow-ipc-v1Flechette TablemarimoValue 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.

StateMeaningaria-busy
connectingThe host is waiting for projection resolution or a runtime connectiontrue
loadingThe producer or selected result is pendingtrue
staleThe host retains a previous result while a current result is pendingtrue for output and value hosts
readyThe host displays the current resultRemoved
missingA complete-cell target has no current resultRemoved
errorResolution, execution, rendering, duplication, or decoding failedRemoved

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 ​

EventDispatchDetail fields
marimo-cell-readyFirst transition to readyalias, runtimeId, outputMime, or diagnostic fields when present
marimo-cell-updatedA later transition to readySame shape as marimo-cell-ready
marimo-cell-errorFirst transition into the current error statealias, 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.

EventDispatchDetail fields
marimo-output-readyFirst transition to readyselector, cellId, mimetype, or diagnostic fields when present
marimo-output-updatedA later transition to readySame shape as marimo-output-ready
marimo-output-errorFirst transition into the current error stateselector, cellId, code, message, hint when present

Value events ​

EventDetail
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:

tsx
<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:

html
<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:

html
<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 fileThe host shows
application/pdfThe 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/htmlThe page in a sandboxed frame with no access to the view
Another media typeThe 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:

html
<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.

FieldValue
renderThe render's source file
bundleThe content identity of the files
mainPath of the main file
previewPath of the preview file, or null
files{ path, mediaType, size, url } for every file
warningsRender warnings as diagnostics
absentTargets the render read without a current result, shown as default
regionsParts 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:

SurfaceRegions shown
A PDF page the host showsThe regions of that page
An image or video the host showsThe regions of the file
An img or video in the page's own markupThe regions of the bundle file its src names
An element with data-marimo-render-fileThe 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:

html
<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 ​

typst
#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:

RuntimeResults come fromFiles
Python, edit and run modeThe reader's kernel session, read by StudioRendered by Studio when a result changes
Browser, edit modeThe notebook state in the editor tabRendered by Studio when a result changes
Prepared, edit modeThe prepared state the preview showsRendered by Studio for each state change
Static export, zero-pythonEach prepared notebook stateOne bundle per state, rendered at export
Browser, run modeUnavailablerender-results-unavailable error
Static export, wasmUnavailablerender-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:

AttributeOnValue and effect
data-marimo-lens-inputsAn element that page code draws intoHost 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-labelA host or an annotated elementPlain text that replaces the label Lens shows while selecting. The selection's notebook cells stay the same
data-marimo-lens-detailA host or an annotated elementPlain text shown under the label, such as the row a selection names
data-marimo-lens-scope#app-shellA 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-targetAn authored element with a stable idMakes the element a target of its own, so Lens reconnects feedback to it across rebuilds
data-marimo-lens-render-sourceAn authored targetJSON such as {"path":"index.html"} naming the source file that holds the element
data-marimo-lens-contextA chart, card, or sectionMarks 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.