Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

`marimo export pdf` drops Altair charts that are a cell's bare output (and `mo.ui.altair_chart`)

Open Beginner friendly
#11,539 1 comment 0 reactions 0 assignees View on GitHub

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

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

bug
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/html outputs, which include every layout such as mo.vstack, are rendered by HtmlOutput with className={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> inside Suspense, with no wrapper (frontend/src/components/editor/Output.tsx:211-229). The DOM is #output-<id> > div.vega-embed, with no .output child.

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from marimo-team/marimo

All issues in marimo-team/marimo

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.