streamlit-to-marimo▌
marimo-team/skills · updated Apr 8, 2026
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.
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.
Ratings
4.4★★★★★34 reviews- ★★★★★Nia Perez· Dec 28, 2024
I recommend streamlit-to-marimo for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
- ★★★★★William Rahman· Dec 24, 2024
streamlit-to-marimo fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.
- ★★★★★Shikha Mishra· Dec 16, 2024
Useful defaults in streamlit-to-marimo — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
- ★★★★★Advait Harris· Dec 8, 2024
We added streamlit-to-marimo from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
- ★★★★★Michael Menon· Nov 19, 2024
Solid pick for teams standardizing on skills: streamlit-to-marimo is focused, and the summary matches what you get after install.
- ★★★★★Yash Thakker· Nov 7, 2024
streamlit-to-marimo has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Dhruvi Jain· Oct 26, 2024
Solid pick for teams standardizing on skills: streamlit-to-marimo is focused, and the summary matches what you get after install.
- ★★★★★Harper Yang· Oct 10, 2024
streamlit-to-marimo has been reliable in day-to-day use. Documentation quality is above average for community skills.
- ★★★★★Rahul Santra· Sep 17, 2024
streamlit-to-marimo is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.
- ★★★★★Noah Park· Sep 17, 2024
streamlit-to-marimo reduced setup friction for our internal harness; good balance of opinion and flexibility.
showing 1-10 of 34