$npx -y skills add pymc-labs/python-analytics-skills --skill marimo-notebookALWAYS use when: creating/editing marimo notebooks, working with any .py file containing @app.cell decorators, building reactive Python notebooks, doing exploratory data analysis in notebook form, converting Jupyter (.ipynb) to marimo, or when user mentions "marimo", "reactive no
| 1 | # Marimo Notebooks |
| 2 | |
| 3 | Marimo notebooks are reactive Python notebooks stored as pure `.py` files. Cells auto-execute when dependencies change, modeled as a directed acyclic graph (DAG). |
| 4 | |
| 5 | ## Core Concepts |
| 6 | |
| 7 | ### Reactivity Model |
| 8 | - marimo uses **static analysis** to build a dependency graph from variable references and definitions |
| 9 | - When a cell runs, all cells referencing its defined variables **automatically re-run** |
| 10 | - Execution order follows the dependency graph, **not visual cell order** |
| 11 | - Each global variable must be defined by **exactly one cell** |
| 12 | - marimo does **not track object mutations** (like `list.append()`)—mutate in the same cell that creates the object, or create new variables |
| 13 | |
| 14 | ### Avoiding Variable Name Conflicts |
| 15 | Each global variable must be defined by exactly one cell. Two strategies: |
| 16 | |
| 17 | **1. Wrap code in functions (preferred for reusable patterns):** |
| 18 | ```python |
| 19 | @app.cell |
| 20 | def _(data): |
| 21 | def compute_mean_with_new_col(df): |
| 22 | temp = df.copy() |
| 23 | temp["new_col"] = temp["x"] * 2 |
| 24 | return temp.mean() |
| 25 | |
| 26 | return (compute_mean_with_new_col(data),) |
| 27 | ``` |
| 28 | |
| 29 | **2. Use meaningful, unique variable names:** |
| 30 | ```python |
| 31 | @app.cell |
| 32 | def _(model1_data): |
| 33 | model1_transformed = model1_data.copy() |
| 34 | model1_transformed["new_col"] = model1_transformed["x"] * 2 |
| 35 | return (model1_transformed,) |
| 36 | ``` |
| 37 | |
| 38 | Never use underscore prefixes to generate unique variable names. No exceptions. |
| 39 | |
| 40 | ## Notebook Structure |
| 41 | |
| 42 | ```python |
| 43 | import marimo |
| 44 | |
| 45 | __generated_with = "0.10.0" |
| 46 | app = marimo.App(width="medium") |
| 47 | |
| 48 | @app.cell |
| 49 | def _(): |
| 50 | import marimo as mo |
| 51 | return (mo,) |
| 52 | |
| 53 | @app.cell |
| 54 | def _(mo): |
| 55 | mo.md("# Hello") |
| 56 | return |
| 57 | |
| 58 | if __name__ == "__main__": |
| 59 | app.run() |
| 60 | ``` |
| 61 | |
| 62 | Key rules: |
| 63 | - Each cell is a function decorated with `@app.cell` |
| 64 | - Variables shared by returning tuples: `return (var1, var2,)` |
| 65 | - Cells receive variables as parameters: `def _(mo, df):` |
| 66 | - Execution order follows dependency graph, not position |
| 67 | - Name cells descriptively for CellTour targeting: `def model_specification():` |
| 68 | |
| 69 | ## CLI Commands |
| 70 | |
| 71 | ```bash |
| 72 | # Create & Edit |
| 73 | marimo new # Create new notebook |
| 74 | marimo edit notebook.py # Open editor |
| 75 | marimo edit notebook.py --watch # Live reload on file changes |
| 76 | |
| 77 | # Run as App |
| 78 | marimo run notebook.py # Run as app (code hidden by default) |
| 79 | marimo run notebook.py --include-code # Show code in app view |
| 80 | |
| 81 | # Convert |
| 82 | marimo convert notebook.ipynb -o notebook.py # Jupyter to marimo |
| 83 | |
| 84 | # Export |
| 85 | marimo export html notebook.py -o out.html # Static HTML |
| 86 | marimo export ipynb notebook.py -o out.ipynb # To Jupyter |
| 87 | |
| 88 | # Validate |
| 89 | marimo check notebook.py # Lint and validate |
| 90 | marimo check notebook.py --fix # Auto-fix issues |
| 91 | ``` |
| 92 | |
| 93 | ## Code Visibility in Run Mode |
| 94 | |
| 95 | **CRITICAL FOR TUTORIALS**: By default, `marimo run` hides code. Use `mo.show_code()` to display it. |
| 96 | |
| 97 | ### mo.show_code() - Per-Cell Display |
| 98 | |
| 99 | **IMPORTANT**: Call `mo.show_code()` as a **statement on its own line**, NOT in the return statement. |
| 100 | |
| 101 | ```python |
| 102 | @app.cell |
| 103 | def model_definition(mo, pm, X, y): |
| 104 | with pm.Model() as model: |
| 105 | alpha = pm.Normal("alpha", mu=0, sigma=10) |
| 106 | beta = pm.Normal("beta", mu=0, sigma=10) |
| 107 | mu = alpha + beta * X |
| 108 | pm.Normal("y", mu=mu, sigma=1, observed=y) |
| 109 | |
| 110 | # Show this cell's code alongside its output |
| 111 | mo.show_code(model, position="above") |
| 112 | return (model,) |
| 113 | ``` |
| 114 | |
| 115 | **WRONG vs RIGHT patterns:** |
| 116 | ```python |
| 117 | # WRONG - do not put mo.show_code() in return statement |
| 118 | return mo.show_code(result) |
| 119 | |
| 120 | # RIGHT - call as statement, then return separately |
| 121 | mo.show_code(result, position="above") |
| 122 | return (result,) |
| 123 | ``` |
| 124 | |
| 125 | - `position="above"` shows code first, then output (best for tutorials) |
| 126 | - `position="below"` shows output first, then code (default) |
| 127 | |
| 128 | ## Markdown with mo.md() |
| 129 | |
| 130 | ```python |
| 131 | @app.cell |
| 132 | def _(mo): |
| 133 | mo.md(r""" |
| 134 | # Title |
| 135 | |
| 136 | Interpolate Python: {slider} |
| 137 | |
| 138 | **LaTeX**: $f(x) = e^x$ |
| 139 | |
| 140 | $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$ |
| 141 | |
| 142 | **Icons**: ::lucide:rocket:: or ::mdi:home:: |
| 143 | """) |
| 144 | return |
| 145 | ``` |
| 146 | |
| 147 | - Use raw strings (`r"""..."""`) for LaTeX |
| 148 | - Interpolate UI elements: `f"Value: {slider}"` |
| 149 | - For complex objects: `f"Plot: {mo.as_html(fig)}"` |
| 150 | |
| 151 | ## UI Components (mo.ui.*) |
| 152 | |
| 153 | ### Basic Inputs |
| 154 | ```python |
| 155 | slider = mo.ui.slider(0, 100, value=50, label="Value") |
| 156 | number = mo.ui.number(0, 100, value=50) |
| 157 | text = mo.ui.text(value="", placeholder="Enter text") |
| 158 | checkbox = mo.ui.checkbox(value=F |