Skip to content

Select results with Lens ​

marimo-lens is a marimo widget that lets you select part of a rendered result and hand it to a coding agent with the notebook code behind it.

With Lens, you point at a result instead of describing it. Select part of a view, add a note such as "Why does Q3 dip?", and the agent receives your selection. In Studio you can select a placed cell, a number on a page, a bar in a chart that page code draws, or a figure inside a Typst PDF.

Each selection carries two kinds of evidence. Lens records what you pointed at. Studio adds where it came from: the cell that computed it, the value it shows, and the place in the view project that shows it. With both, the agent can tell whether a request belongs in the notebook or in the view.

PreviewLens · Select
S1Selection summary.orders
Target
Value
Computed by
summary
Upstream
region_control, orders
Narrowed to
summary.orders
Placed by
<strong mo-value="summary.orders">

NotebookHow summary counts orders.

ViewThe label and number format, in index.html.

Select a part of either view. Lens records what you selected, and Studio adds the cell that computed it and the place in the view that shows it.

Set up Lens ​

Install Lens in the notebook's Python environment, and restart a running notebook afterwards:

console
uv pip install "marimo-studio[lens]"

For a sandboxed notebook, add marimo-studio[lens] to the script's dependencies instead. When the notebook creates no Lens of its own, marimo mounts one, and Notebook and Preview each show a Lens dock, the panel that lists your selections. Both docks share one Lens, so a selection made in a view reaches the same agent as a selection made in the notebook.

Lens hands selections to a code-mode agent, an agent that runs Python in the live notebook, such as one connected through marimo pair or marimo's AI sidebar. Author with a coding agent connects one. Then select a result, add a note, and ask:

Use Lens to address my current selection in this Studio view.

What a selection traces to ​

Every part of a view is a Lens target. What the selection traces to depends on how the view shows the part:

You selectIn the view sourceThe selection traces to
A placed cell<marimo-cell name="region_control">The cell region_control and the cells upstream of it
An output<marimo-output value="quarterly_chart">The variable quarterly_chart and the cell that defines it
A value<strong mo-value="summary.orders">The value selector summary.orders and the cell summary
A chart that page code drawsAn element with data-marimo-lens-inputsThe hosts it lists, with their selectors and cells
Part of a PDF or a videoA render regionThe value selector, such as report.rows[2].orders, its cell, and the source line, such as main.typ:41
Text or layout with no inputsAny element inside #app-shellIts source file, with no notebook cells

Lens follows marimo's dataflow graph from the producing cell to the cells and control values upstream of it, and an image of the selected target joins the selection when the browser can capture one. The Lens documentation lists everything an agent receives.

How Studio connects a selection to the notebook ​

Lens on its own knows notebook outputs by the cell that renders them. A view shows results outside the notebook, so Studio supplies the link for each kind of target:

  • Hosts. Studio resolves every host to its target and producing cell when the page opens, and marks the host with both. Selecting a host reads those marks, so the view source carries no extra attributes.
  • Page code. Studio cannot follow JavaScript from a value to the element it draws. A region that page code draws lists its inputs with data-marimo-lens-inputs, and the selection takes their cells.
  • Rendered files. A PDF or a video is a picture of results. When the provider renders the file, it reports a render region for each placed result: a rectangle on a page, an image, or a span of video frames, with the results it shows and the line that placed them. Studio lays an invisible box over each region, and Lens selects the boxes.
  • Authored content. Headings, prose, and layout read no results. Lens selects them by their nearest section or card, and the selection names the source file so the agent edits the view.

Make page code selectable ​

<marimo-cell>, <marimo-output>, and mo-value hosts are selectable without extra markup. A chart or component that page code draws stays connected through hidden hosts. List them on the element that shows the drawing:

html
<span id="rows-data" hidden mo-value="rows"></span>
<span id="summary-data" hidden mo-value="summary.total"></span>
<section data-marimo-lens-inputs="rows-data summary-data">
  <!-- JavaScript renders the chart here. -->
</section>

data-marimo-lens-inputs lists host IDs in the same document, separated by spaces, and declares the region's complete set of notebook inputs. Lens reads each host's resolved selector and producing cell. A reference can also point to a <marimo-output> or <marimo-cell> host. A missing, duplicate, or unbound reference makes the region unavailable, and a reference cannot pass through another annotated region.

Update the references when the drawing's inputs change, including transformed inputs and portals. Set aria-busy="true" while the drawing renders and clear it when it finishes. A chart drawn on a canvas is one target unless its renderer adds finer DOM targets.

Select one metric ​

Give each metric its own hidden host, so a selection names its exact field:

html
<article class="metric">
  <span hidden mo-value="summary.events"></span>
  <span>Events</span>
  <strong><!-- JavaScript formats the value here. --></strong>
</article>

Use a visible mo-value when its plain text needs no formatting. For a value without a JSON form, reference a hidden <marimo-output> host through data-marimo-lens-inputs.

A selector that page state computes, such as one row of a table, needs the Python or Browser runtime, because a prepared export needs a fixed set of targets. In a prepared view, place the whole collection with one selector and reference that host from each row, with the row in data-marimo-lens-detail.

Label a selection ​

While you select, Lens outlines the target and labels its edge with the cell or variable name Studio resolved. Set a label and a detail line when a region needs a clearer name:

html
<section
  data-marimo-lens-inputs="rows-data summary-data"
  data-marimo-lens-label="Revenue forecast"
  data-marimo-lens-detail="Monthly revenue · selected region"
>
  <!-- Custom rendering -->
</section>

Labels change what Lens shows. The selection's notebook cells stay the same.

Mark authored regions ​

Text and layout inside #app-shell are selectable. Lens groups a click into the nearest section, card, figure, or block. Set the grouping selector on the app shell:

html
<main id="app-shell" data-marimo-lens-scope=".card, header, figure">
  <!-- Authored regions are selectable. -->
</main>

Give a region its own identity with data-marimo-lens-target and a stable id, so Lens can reconnect feedback after a rebuild:

html
<header
  id="intro"
  data-marimo-lens-target
  data-marimo-lens-label="Introduction"
  data-marimo-lens-render-source='{"path":"index.html"}'
>
  <h1>Regional outlook</h1>
</header>

data-marimo-lens-render-source names the file that holds the region. Add data-marimo-lens-inputs when the region also shows notebook results.

Mark parts of a PDF ​

The Typst and LaTeX providers locate each figure and cell a document places, so those parts are selectable without extra markup. Wrap text or table cells built from values to make them selectable too:

typst
#import "marimo.typ": marimo_region, marimo_value

#marimo_region("summary.rooms", label: "Rooms in use")[
  Rooms in use: #marimo_value("summary.rooms", default: 0)
]
latex
Rooms in use: \marimoregion[Rooms in use]{summary.rooms}{\marimovalue{summary.rooms}}

Each selector names a value the document reads, optionally narrowed with .field and [index] steps, such as rows[2].orders. A region keeps its body on one page, so mark the cells of a long table one by one. Views created before regions existed need the current marimo.typ or marimo.sty from a new view's starter.

Selecting the file outside its regions traces it to every result the render reads, up to 64 of them. In a video, the boxes follow the playing time. Preview runs rendered files in an isolated origin, so Lens captures no image of a video frame. The selection keeps its cells, label, and source line.

A view provider for another tool reports regions from render(). Locate parts of rendered files defines the record.

Put a Lens dock in a live view ​

Preview reuses the Lens that marimo mounts in the notebook. To give readers of a view served by marimo run their own dock, create the Lens in the notebook:

python
from marimo_lens import Lens
from marimo_studio import STUDIO_RESULT_SELECTOR

studio_lens = Lens(dom_selector=STUDIO_RESULT_SELECTOR)
None

The final None keeps the cell's output empty. Place the Lens in the view:

html
<marimo-output value="studio_lens"></marimo-output>

STUDIO_RESULT_SELECTOR matches hosts, elements with declared inputs, explicit authored targets, and render regions. marimo skips its own Lens in a notebook that creates one, so studio_lens is the notebook's only Lens.

Notebook result projections lists every Lens attribute, and the Lens agent guide covers how an agent inspects, resolves, and reports on selections.