`marimo export pdf` drops Altair charts that are a cell's bare output (and `mo.ui.altair_chart`)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 80/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- playwright, python
- Domain
- backend, data-visualization
Research direction
Start in marimo/_export/_pdf_raster.py at _capture_pngs_from_page and inspect _wait_for_target_ready; compare its locator with the Vega embed fallback in marimo/_code_mode/screenshot.py. Update the export target selection to capture bare Vega and Vega-Lite outputs, then run the focused selector and progress tests mentioned in the issue. Done means bare Altair and mo.ui.altair_chart outputs appear in rasterized PDFs without the readiness timeout or nbconvert warnings.
Written by the indexing model from the issue text.
Description
Describe the bug
With the default rasterized PDF export, an Altair chart that is the last expression of a cell is missing from the PDF. The same goes for mo.ui.altair_chart(chart). Wrapping the chart in a layout (mo.vstack([chart])) works. For each missing chart, the export stalls for about 90 s and nbconvert then warns:
UserWarning: Your element with mimetype(s) dict_keys(['application/vnd.vegalite.v6+json']) is not able to be represented.
UserWarning: Your element with mimetype(s) dict_keys(['application/vnd.vega.v6+json']) is not able to be represented.
Reproduction
See below.
Root cause
_capture_pngs_from_page screenshots each target with
locator = page.locator(f"#output-{target.cell_id} > .output").first
(marimo/_export/_pdf_raster.py:643 on main). The .output element only exists for some mimetypes:
text/htmloutputs, which include every layout such asmo.vstack, are rendered byHtmlOutputwithclassName={channel}, which produces<div class="output block">(frontend/src/components/editor/Output.tsx:113-125,frontend/src/components/editor/output/HtmlOutput.tsx:20).- Vega/Vega-Lite mimetypes are rendered as a bare
<LazyVegaEmbed>insideSuspense, with no wrapper (frontend/src/components/editor/Output.tsx:211-229). The DOM is#output-<id> > div.vega-embed, with no.outputchild.
A bare alt.Chart is formatted as application/vnd.vegalite.v6+json (marimo/_output/formatters/altair_formatters.py:68-76). mo.ui.altair_chart switches to application/vnd.vega.v6+json when is_non_interactive() is true, which is the case during export (marimo/_plugins/ui/_impl/altair_chart.py:658-664). Both are collected as raster targets, but the locator never matches. _wait_for_target_ready waits out _READINESS_TIMEOUT_MS (90 s), the target is skipped, and nbconvert can't render the raw Vega mimetype.
marimo/_code_mode/screenshot.py:327-329 already falls back to > .vega-embed for the same reason.
Proposed fix
Match either node:
- locator = page.locator(f"#output-{target.cell_id} > .output").first
+ locator = page.locator(_output_selector(target.cell_id)).first
with
def _output_selector(cell_id: CellId_t) -> str:
container = f"#output-{cell_id}"
return f"{container} > .output, {container} > .vega-embed"
.vega-embed is a concrete inline-block element sized to the chart, so the screenshot is cropped to the chart. Layout outputs still match > .output. Their Vega embeds are nested deeper, not direct children, so the comma selector can't pick a different node for them. I've also updated the existing test_capture_pngs_from_page_reports_progress_in_notebook_order assertion and added a small unit test for the selector.
I tested this locally by swapping the patched _pdf_raster.py into marimo 0.25.1 (the file is identical on main). All three charts show up in the PDF, and the nbconvert warnings go away.
An alternative is a frontend change: wrap the Vega branch of OutputRenderer in a <div className={channel}> so it matches HtmlOutput. That would make every mimetype produce .output, but it touches the live editor DOM and CSS (.output > *, .marimo-cell.published .output-area .output margins). The selector change is smaller and only affects export.
Will you submit a PR?
- Yes
Environment
{
"marimo": "0.25.1",
"editable": false,
"location": "",
"OS": "Darwin",
"OS Version": "25.6.0",
"Processor": "arm",
"Python Version": "3.13.15",
"Locale": "en_US",
"Binaries": {
"Browser": "156.0.8078.49",
"Node": "v24.21.0",
"uv": "0.12.5 (210d1f678 2026-08-14 aarch64-apple-darwin)"
},
"Dependencies": {
"click": "8.3.3",
"docutils": "0.23",
"itsdangerous": "2.2.0",
"jedi": "0.20.0",
"markdown": "3.10.3",
"narwhals": "2.25.0",
"packaging": "26.3",
"psutil": "7.2.2",
"pygments": "2.21.0",
"pymdown-extensions": "10.21.3",
"pyyaml": "6.0.3",
"starlette": "1.6.0",
"tomlkit": "0.15.1",
"typing-extensions": "4.16.0",
"uvicorn": "0.52.4",
"websockets": "16.1.1"
},
"Optional Dependencies": {
"altair": "6.2.2",
"anywidget": "0.11.0",
"duckdb": "1.5.5",
"loro": "1.13.2",
"nbformat": "5.11.1",
"openai": "3.3.1",
"pandas": "2.3.3",
"polars": "1.43.2",
"pyarrow": "23.0.1",
"pytest": "8.4.2",
"ruff": "0.15.22",
"sqlglot": "30.17.0",
"vegafusion": "2.0.3",
"watchdog": "6.0.0"
},
"Experimental Flags": {}
}
Code to reproduce
import marimo
app = marimo.App()
@app.cell
def _():
import altair as alt
import marimo as mo
import pandas as pd
df = pd.DataFrame({"x": list(range(10)), "y": [v * v for v in range(10)]})
def make_chart(title):
return alt.Chart(df, title=title).mark_line(point=True).encode(x="x", y="y")
return make_chart, mo
@app.cell
def _(make_chart):
make_chart("CHART A - bare alt.Chart")
return
@app.cell
def _(make_chart, mo):
mo.ui.altair_chart(make_chart("CHART B - mo.ui.altair_chart"))
return
@app.cell
def _(make_chart, mo):
mo.vstack([make_chart("CHART C - mo.vstack")])
return
marimo export pdf repro.py -o repro.pdf. Only CHART C is in the PDF.
- Dominant language
- Python
- Stars
- 22.9k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 281
Getting set up
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from marimo-team/marimo
-
Cell referencing a variable from a "cancelled" cell runs and raises NameError instead of being blockedPossibly taken @dmadisetti claimed this 11 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
marimo-team/marimo#11307 · 1 assignee ·
Maintainers usually reply within 1 day
-
molab
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
marimo-team/marimo#11298 ·
Maintainers usually reply within 1 day
-
Theme change does not affect terminal colorsMay be free again @CloudCorpRecords claimed this 13 days ago, and no pull request is open. Openneeds discussion
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
marimo-team/marimo#11244 · 3 comments ·
Maintainers usually reply within 1 day
-
enhancement needs discussion
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
marimo-team/marimo#10960 · 4 comments ·
Maintainers usually reply within 1 day
-
enhancement needs discussion
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
marimo-team/marimo#10787 · 5 comments · 1 reaction ·
Maintainers usually reply within 1 day
All issues in marimo-team/marimo
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
aws-samples/sample-per-model-bedrock#639 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 1/5 Under an hour Newbie friendliness 86/100
awslabs/visual-asset-management-system#937 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 79/100
project-codeflare/codeflare-sdk#1195 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
pandas-dev/pandas-stubs#2051 ·
Maintainers usually reply within 1 day