streamlit-to-marimo
Converting Streamlit Apps to Marimo
For general marimo notebook conventions (cell structure, PEP 723 metadata, output rendering, marimo check, variable naming, etc.), refer to the marimo-notebook skill. This skill focuses specifically on mapping Streamlit concepts to marimo equivalents.
Steps
-
Read the Streamlit app to understand its widgets, layout, and state management.
-
Create a new marimo notebook following the
marimo-notebookskill conventions. Add all dependencies the Streamlit app uses (pandas, plotly, altair, etc.) — but replacestreamlitwithmarimo. You should not overwrite the original file. -
Map Streamlit components to marimo equivalents using the reference tables below. Key principles:
- UI elements are assigned to variables and their current value is accessed via
.value. - Cells that reference a UI element automatically re-run when the user interacts with it — no callbacks needed.
- UI elements are assigned to variables and their current value is accessed via
-
Handle conceptual differences in execution model, state, and caching (see below).
-
Run
uvx marimo checkon the result and fix any issues.
Widget Mapping Reference
Input Widgets
| Streamlit | marimo | Notes |
|---|---|---|
st.slider() |
mo.ui.slider() |
|
st.select_slider() |
mo.ui.slider(steps=[...]) |
Pass discrete values via steps |
st.text_input() |
mo.ui.text() |
|
st.text_area() |
mo.ui.text_area() |
|
st.number_input() |
mo.ui.number() |
|
st.checkbox() |
mo.ui.checkbox() |
|
st.toggle() |
mo.ui.switch() |
|
st.radio() |
mo.ui.radio() |
|
st.selectbox() |
mo.ui.dropdown() |
|
st.multiselect() |
mo.ui.multiselect() |
|
st.date_input() |
mo.ui.date() |
|
st.time_input() |
mo.ui.text() |
No dedicated time widget |
st.file_uploader() |
mo.ui.file() |
Use .contents() to read bytes |
st.color_picker() |
mo.ui.text(value="#000000") |
No dedicated color picker |
st.button() |
mo.ui.button() or mo.ui.run_button() |
Use run_button for triggering expensive computations |
st.download_button() |
mo.download() |
Returns a download link element |
st.form() + st.form_submit_button() |
mo.ui.form(element) |
Wraps any element so its value only updates on submit |
Display Elements
| Streamlit | marimo | Notes |
|---|---|---|
st.write() |
mo.md() or last expression |
|
st.markdown() |
mo.md() |
Supports f-strings: mo.md(f"Value: {x.value}") |
st.latex() |
mo.md(r"$...$") |
marimo uses KaTeX; see references/latex.md |
st.code() |
mo.md("```python\n...\n```") |
|
st.dataframe() |
df (last expression) |
DataFrames render as interactive marimo widgets natively; use mo.ui.dataframe(df) only for no-code transformations |
st.table() |
df (last expression) |
Use mo.ui.table(df) if you need row selection |
st.metric() |
mo.stat() |
|
st.json() |
mo.json() or mo.tree() |
mo.tree() for interactive collapsible view |
st.image() |
mo.image() |
|
st.audio() |
mo.audio() |
|
st.video() |
mo.video() |
Charts
| Streamlit | marimo | Notes |
|---|---|---|
st.plotly_chart(fig) |
fig (last expression) |
Use mo.ui.plotly(fig) for selections |
st.altair_chart(chart) |
chart (last expression) |
Use mo.ui.altair_chart(chart) for selections |
st.pyplot(fig) |
fig (last expression) |
Use mo.ui.matplotlib(fig) for interactive matplotlib |
Layout
| Streamlit | marimo | Notes |
|---|---|---|
st.sidebar |
mo.sidebar([...]) |
Pass a list of elements |
st.columns() |
mo.hstack([...]) |
Use widths=[...] for column ratios |
st.tabs() |
mo.ui.tabs({...}) |
Dict of {"Tab Name": content} |
st.expander() |
mo.accordion({...}) |
Dict of {"Title": content} |
st.container() |
mo.vstack([...]) |
|
st.empty() |
mo.output.replace() |
|
st.progress() |
mo.status.progress_bar() |
|
st.spinner() |
mo.status.spinner() |
Context manager |
Key Conceptual Differences
Execution Model
Streamlit reruns the entire script top-to-bottom on every interaction. Marimo uses a reactive cell DAG — only cells that depend on changed variables re-execute.
- No need for
st.rerun()— reactivity is automatic. - No need for
st.stop()— structure cells so downstream cells naturally depend on upstream values.
State Management
| Streamlit | marimo |
|---|---|
st.session_state["key"] |
Regular Python variables between cells |
Callback functions (on_change) |
Cells referencing widget.value re-run automatically |
st.query_params |
mo.query_params |
Caching
| Streamlit | marimo |
|---|---|
@st.cache_data |
@mo.cache |
@st.cache_resource |
@mo.persistent_cache |
@mo.cache is the primary caching decorator — it works like functools.cache but is aware of marimo's reactivity. @mo.persistent_cache goes further by persisting results to disk across sessions, useful for expensive computations like model training.
Multi-Page Apps
Marimo offers two approaches for multi-page Streamlit apps:
- Single notebook with routing: Use
mo.routeswithmo.nav_menuormo.sidebarto build multiple "pages" (tabs/routes) inside one notebook. - Multiple notebooks as a gallery: Run a folder of notebooks with
marimo run folder/to serve them as a gallery with navigation.
Deploying
marimo features molab to host marimo apps instead of the streamlit community cloud. You can generate an "open in molab" button via the add-molab-badge skill.
Custom components
streamlit has a feature for custom components. These are not compatible with marimo. You might be able to generate an equivalent anywidget via the marimo-anywidget skill but discuss this with the user before working on that.
More from daviddwlee84/agent-skills
project-knowledge-harness
Set up a structured project memory for any software project — TODO.md as priority/effort-tagged index of future work, backlog/ for resume-friendly research/design notes on P? items, and pitfalls/ as a symptom-grep-able knowledge base of past traps. Use when a user wants somewhere to record "maybe later" ideas, freeze troubleshooting state, capture trade-off analysis, or stop re-debugging the same problem.
15agent-history-hygiene
Commit SpecStory chat transcripts (`.specstory/history/*.md`), Claude Code plan files (`.claude/plans/*.md`, `plansDirectory`), and other coding-agent artifacts (`.cursor/plans/`, `.cursor/rules/`, `.opencode/plans/`, `.specify/`, `.codex/`) alongside the feature diff they produced — without leaking `.env` contents, API keys, or private-key PEM blocks into git history. Use when the user says "commit my chat", "save this specstory session", "stage the plan file", "scrub the transcript", "my .env leaked in chat", "bootstrap pre-commit for this project", or when you notice untracked `.specstory/history/*.md` or `.claude/plans/*.md` files while running `git status`. Also use after an accidental push of a secret to enforce rotate-first, rewrite-last remediation instead of reflexive `git push --force`.
11mkdocs-site-bootstrap
Bootstrap MkDocs Material docs sites with optional GitHub Pages deploy, uv-pinned tooling, llms.txt/copy-to-LLM support, page/nav helpers, and mkdocs-static-i18n languages such as zh-TW. Use when the user asks to set up docs, publish docs to GitHub Pages, create an MkDocs site, turn README or markdown notes into a site, add bilingual/multilingual docs, add zh-TW/Traditional Chinese, i18n, or translate docs. Consent-gated; records repo preferences and never auto-migrates existing docs.
11pueue-job-queue
Drive Nukesor/pueue (https://github.com/Nukesor/pueue) for queued, parallel, scheduled, and lightly-DAG'd shell jobs — wraps `pueue add --after`, `pueue status --json`, `pueue log --json`, group-level parallelism, and `pueued` daemon health. Use when the user wants to background long-running shell commands across reboots, queue dozens of jobs with capped parallelism, run a fan-out / fan-in pipeline of shell steps, says "pueue", "pueued", "pueue add", "pueue queue", "pueue group", "task queue for shell", "background this job", or asks how to schedule/parallelize CLI work without a real orchestrator (Airflow/Prefect/Dagster). Good fit for ML sweeps, long-running data pipelines, batched evaluations, scheduled `--delay` jobs, "wait for X then run Y" sequences.
4skill-author
Author a new agent skill or refactor an existing one to follow agentskills.io best practices — gotchas sections, output templates, validation loops, calibrated specificity (fragility-based), and agentic script design (--help, --dry-run, structured stdout, stderr diagnostics, PEP 723 inline deps, pinned uvx/npx versions). Use whenever the user wants to create a new skill from scratch, scaffold a SKILL.md, write a reference file, design a script meant to be invoked by an agent, lint a draft skill for quality, or convert an ad-hoc workflow into a reusable skill. For evaluating skill output quality with test cases, benchmarking, or optimizing the description trigger rate, defer to the `skill-creator` skill instead — this skill focuses on authoring, not evaluation.
2dvc-ml-workflow
Set up and operate a DVC (Data Version Control) workflow for ML projects — `dvc init`, `dvc.yaml` pipelines, `params.yaml`, `dvc exp run --queue` for parallel sweeps with metrics auto-bound to ephemeral git commits, and remote storage (S3/SSH/GDrive). Use whenever the user wants reproducible ML pipelines, data/model versioning that lives alongside git, parameter sweeps without standing up a tracking server, queued/parallel experiment execution, or asks about `dvc.yaml` / `dvc exp run` / `dvc queue` / `params.yaml` / `dvc add` / `dvc push` / `.dvc/cache`. Always references the official docs at https://dvc.org/doc and the upstream repo https://github.com/treeverse/dvc (Iterative was acquired by Treeverse in 2024 — `pip install dvc` resolves to this repo).
2