Marimo - A reactive Python notebook that is reproducible, git-friendly, and deployable as scripts or apps
Installation
|
Install (minimal) pip install marimo # or uv add marimo # or conda install -c conda-forge marimo |
|
|
Install (recommended extras) pip install "marimo[recommended]" # or uv add "marimo[recommended]" Includes DuckDB, Altair, Polars, Ruff, and more. |
|
|
Verify installation with tutorial marimo tutorial intro
# or with uv
uv run marimo tutorial intro |
CLI Commands
|
Create and edit a notebook marimo edit notebook.py Creates a new notebook or opens an existing one in the browser. |
|
|
Run notebook as app marimo run notebook.py Runs the notebook as a read-only web app. |
|
|
Run notebook as a script python notebook.py Execute notebook cells top-to-bottom as a standard Python script. |
|
|
Export to HTML marimo export html notebook.py -o output.html |
|
|
Export to script marimo export script notebook.py -o script.py |
|
|
Convert Jupyter notebook to Marimo marimo convert notebook.ipynb -o notebook.py |
|
|
New notebook (no file) marimo edit Opens a new untitled notebook in the browser. |
Core Concepts
|
Import marimo import marimo as mo The single entry point for all Marimo features. |
|
|
Reactive cells Cells are connected via a directed acyclic graph based on variable definitions and references. When a cell runs, all downstream cells that depend on its variables automatically re-run.
|
|
|
Displaying output # The last expression in a cell is displayed automatically mo.md("Hello, **world**!") # Use mo.output.append() to display multiple outputs mo.output.append(mo.md("First")) mo.output.append(mo.md("Second")) |
|
|
Mutation caveat Marimo does not track object mutations. Mutating a variable in a different cell than where it was defined will not trigger reactive updates. # BAD: define in cell A, mutate in cell B # GOOD: define and mutate in the same cell df["new_col"] = df["a"] + df["b"] # same cell as df definition |
Markdown & Display
|
Render markdown mo.md("# Heading\n\nSome **bold** and _italic_ text.") |
|
|
Interpolate Python values in markdown name = "Alice" mo.md(f"Hello, **{name}**!") Embed UI elements or computed values directly into markdown. |
|
|
Embed UI element in markdown slider = mo.ui.slider(1, 10) mo.md(f"Pick a value: {slider}") |
|
|
Render LaTeX # Inline math (use raw string for backslashes) mo.md(r"The formula is $E = mc^2$") # Display math mo.md(r"$$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$") |
|
|
Render icon mo.icon("lucide:rocket") mo.icon("lucide:leaf", size=20, color="green") Uses the Iconify library. Format: |
|
|
Callout box mo.callout(mo.md("**Warning:** This is important!"), kind="warn") # kind: "info" | "success" | "warn" | "danger" |
|
|
Stat display mo.stat(value="42", label="Total users", caption="as of today") |
Layout
|
Horizontal stack mo.hstack([mo.md("Left"), mo.md("Right")]) mo.hstack([item1, item2], gap=2, justify="space-between") |
|
|
Vertical stack mo.vstack([mo.md("Top"), mo.md("Bottom")]) mo.vstack([item1, item2], gap=1, align="stretch") |
|
|
Accordion (collapsible) mo.accordion({ "Section 1": mo.md("Content of section 1"), "Section 2": mo.md("Content of section 2"), }) |
|
|
Tabs tabs = mo.ui.tabs({ "Tab A": mo.md("Content A"), "Tab B": mo.md("Content B"), }) tabs # Access selected tab: tabs.value |
|
|
Sidebar mo.sidebar(mo.md("Sidebar content")) |
|
|
Align content mo.center(mo.md("Centered text")) mo.left(mo.md("Left-aligned")) mo.right(mo.md("Right-aligned")) |
|
|
Carousel mo.carousel([mo.md("Slide 1"), mo.md("Slide 2"), mo.md("Slide 3")]) |
|
|
Lazy load mo.lazy(expensive_computation()) Defers rendering until the element is visible in the viewport. |
UI Inputs - Basic
|
Slider slider = mo.ui.slider(start=0, stop=100, step=1, value=50) slider # Access value: slider.value |
|
|
Range slider range_slider = mo.ui.range_slider(start=0, stop=100, value=[20, 80]) range_slider # Access value: range_slider.value # [low, high] |
|
|
Number input num = mo.ui.number(start=0, stop=100, step=0.5, value=10) num # Access value: num.value |
|
|
Text input text = mo.ui.text(placeholder="Enter text...", label="Name") text # Access value: text.value |
|
|
Text area area = mo.ui.text_area(placeholder="Enter multiline text...", rows=5) area # Access value: area.value |
|
|
Checkbox cb = mo.ui.checkbox(label="Enable feature", value=False) cb # Access value: cb.value # True or False |
|
|
Switch sw = mo.ui.switch(label="Dark mode", value=False) sw # Access value: sw.value # True or False |
|
|
Dropdown dd = mo.ui.dropdown( options=["Option A", "Option B", "Option C"], value="Option A", label="Choose one", ) dd # Access value: dd.value |
|
|
Radio buttons radio = mo.ui.radio( options=["Red", "Green", "Blue"], value="Red", label="Pick a color", ) radio # Access value: radio.value |
|
|
Multiselect ms = mo.ui.multiselect( options=["A", "B", "C", "D"], label="Select multiple", ) ms # Access value: ms.value # list of selected items |
UI Inputs - Advanced
|
Button btn = mo.ui.button(label="Click me", kind="success") btn # Access click count: btn.value |
|
|
Run button (prevents auto-execution) run_btn = mo.ui.run_button(label="Run computation") run_btn # Cell with run_btn.value only executes when button is clicked |
|
|
Date picker date = mo.ui.date(label="Pick a date") date # Access value: date.value # datetime.date object |
|
|
Date range picker dr = mo.ui.date_range(label="Date range") dr # Access value: dr.value # (start_date, end_date) |
|
|
File upload upload = mo.ui.file(label="Upload a file", filetypes=[".csv", ".json"]) upload # Access contents: upload.contents() # bytes # Access name: upload.name() |
|
|
File browser browser = mo.ui.file_browser(initial_path=".", multiple=False) browser # Access path: browser.path() |
|
|
Interactive table table = mo.ui.table( data=[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}], selection="single", # "single" | "multi" | None ) table # Access selected rows: table.value |
|
|
Interactive dataframe df_ui = mo.ui.dataframe(df) df_ui # Access transformed dataframe: df_ui.value |
|
|
Code editor editor = mo.ui.code_editor(value="print('hello')", language="python") editor # Access value: editor.value |
|
|
Chat interface def respond(messages, config): return "Echo: " + messages[-1].content chat = mo.ui.chat(respond, prompts=["Hello", "Tell me a joke"]) chat |
|
|
Form (submit on button click) form = mo.ui.text(label="Your name").form() form # Access submitted value: form.value |
|
|
Array of inputs arr = mo.ui.array([mo.ui.slider(1, 10), mo.ui.slider(1, 10)]) arr # Access values: arr.value # list of values |
|
|
Dictionary of inputs d = mo.ui.dictionary({ "x": mo.ui.slider(1, 10), "y": mo.ui.slider(1, 10), }) d # Access values: d.value # {"x": ..., "y": ...} |
|
|
Refresh button refresh = mo.ui.refresh(default_interval="1s") refresh # Cell re-runs at the given interval while refresh is active |
Interactive Plotting
|
Altair chart (interactive) import altair as alt chart = alt.Chart(df).mark_point().encode(x="x", y="y") chart_ui = mo.ui.altair_chart(chart) chart_ui # Access selected points: chart_ui.value # filtered DataFrame |
|
|
Plotly chart (interactive) import plotly.express as px fig = px.scatter(df, x="x", y="y") chart_ui = mo.ui.plotly(fig) chart_ui # Access selected data: chart_ui.value |
|
|
Matplotlib figure import matplotlib.pyplot as plt fig, ax = plt.subplots() ax.plot([1, 2, 3], [4, 5, 6]) mo.ui.matplotlib(fig) |
State Management
|
Create reactive state get_count, set_count = mo.state(0) # Read value current = get_count() # Update value (triggers dependent cells to re-run) set_count(current + 1) |
|
|
State with button get_count, set_count = mo.state(0) increment = mo.ui.button( label="Increment", on_click=lambda _: set_count(get_count() + 1), ) increment Then in another cell:
|
Control Flow
|
Stop execution conditionally mo.stop(condition, output=mo.md("Stopped because condition was met")) Halts the current cell and downstream cells when |
|
|
Show loading spinner with mo.status.spinner(title="Loading..."): result = long_running_function() |
|
|
Progress bar with mo.status.progress_bar(range(100)) as bar: for i in bar: do_work(i) |
SQL
|
Query a DataFrame with SQL In a SQL cell (toggle cell language to SQL), write:
The result is automatically stored in the |
|
|
DuckDB query in Python import duckdb result = duckdb.sql("SELECT * FROM df WHERE age > 30").df() |
Keyboard Shortcuts
|
|
Run cell |
|
|
Run cell and create new cell below |
|
|
Run all stale cells |
|
|
Save notebook |
|
|
Open hotkeys menu |
|
|
Open command palette |
|
|
Find and replace |
|
|
Delete cell |
|
|
Interrupt kernel |
|
|
Toggle terminal |
|
|
Toggle minimap |
|
|
Toggle sidebar |
|
|
Format all cells (with Ruff) |
Running as App
|
Run as web app marimo run notebook.py Serves the notebook as a read-only interactive app. Users can interact with UI elements but cannot edit code. |
|
|
Run app on specific port marimo run notebook.py --port 8080 |
|
|
Pass CLI arguments to notebook # In notebook, access args with: args = mo.cli_args() name = args.get("name", "world") mo.md(f"Hello, {name}!") marimo run notebook.py -- --name Alice |
Notes
- Based on Marimo documentation
- Marimo is open source: github.com/marimo-team/marimo
- Notebooks are stored as pure Python files, making them git-friendly
- CheatSheet author: Ismail Hatim
