Claude-skill-registry-data marimo-notebooks
git clone https://github.com/majiayu000/claude-skill-registry-data
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/marimo-notebooks" ~/.claude/skills/majiayu000-claude-skill-registry-data-marimo-notebooks && rm -rf "$T"
data/marimo-notebooks/SKILL.mdMarimo Notebooks
Marimo notebooks are reactive Python notebooks stored as pure
.py files. Cells auto-execute when dependencies change, modeled as a directed acyclic graph (DAG).
Core Concepts
Reactivity Model
- marimo uses static analysis to build a dependency graph from variable references and definitions
- When a cell runs, all cells referencing its defined variables automatically re-run
- Execution order follows the dependency graph, not visual cell order
- Each global variable must be defined by exactly one cell
- marimo does not track object mutations (like
)—mutate in the same cell that creates the object, or create new variableslist.append()
Avoiding Variable Name Conflicts
Each global variable must be defined by exactly one cell. Two strategies:
1. Wrap code in functions (preferred for reusable patterns):
@app.cell def _(data): def compute_mean_with_new_col(df): temp = df.copy() temp["new_col"] = temp["x"] * 2 return temp.mean() return (compute_mean_with_new_col(data),)
2. Use meaningful, unique variable names:
@app.cell def _(model1_data): model1_transformed = model1_data.copy() model1_transformed["new_col"] = model1_transformed["x"] * 2 return (model1_transformed,)
Never use underscore prefixes to generate unique variable names. No exceptions.
Notebook Structure
import marimo __generated_with = "0.10.0" app = marimo.App(width="medium") @app.cell def _(): import marimo as mo return (mo,) @app.cell def _(mo): mo.md("# Hello") return if __name__ == "__main__": app.run()
Key rules:
- Each cell is a function decorated with
@app.cell - Variables shared by returning tuples:
return (var1, var2,) - Cells receive variables as parameters:
def _(mo, df): - Execution order follows dependency graph, not position
- Name cells descriptively for CellTour targeting:
def model_specification():
CLI Commands
# Create & Edit marimo new # Create new notebook marimo edit notebook.py # Open editor marimo edit notebook.py --watch # Live reload on file changes # Run as App marimo run notebook.py # Run as app (code hidden by default) marimo run notebook.py --include-code # Show code in app view # Convert marimo convert notebook.ipynb -o notebook.py # Jupyter to marimo # Export marimo export html notebook.py -o out.html # Static HTML marimo export ipynb notebook.py -o out.ipynb # To Jupyter # Validate marimo check notebook.py # Lint and validate marimo check notebook.py --fix # Auto-fix issues
Code Visibility in Run Mode
CRITICAL FOR TUTORIALS: By default,
marimo run hides code. Use mo.show_code() to display it.
mo.show_code() - Per-Cell Display
IMPORTANT: Call
mo.show_code() as a statement on its own line, NOT in the return statement.
@app.cell def model_definition(mo, pm, X, y): with pm.Model() as model: alpha = pm.Normal("alpha", mu=0, sigma=10) beta = pm.Normal("beta", mu=0, sigma=10) mu = alpha + beta * X pm.Normal("y", mu=mu, sigma=1, observed=y) # Show this cell's code alongside its output mo.show_code(model, position="above") return (model,)
WRONG vs RIGHT patterns:
# WRONG - do not put mo.show_code() in return statement return mo.show_code(result) # RIGHT - call as statement, then return separately mo.show_code(result, position="above") return (result,)
shows code first, then output (best for tutorials)position="above"
shows output first, then code (default)position="below"
Markdown with mo.md()
@app.cell def _(mo): mo.md(r""" # Title Interpolate Python: {slider} **LaTeX**: $f(x) = e^x$ $$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$$ **Icons**: ::lucide:rocket:: or ::mdi:home:: """) return
- Use raw strings (
) for LaTeXr"""...""" - Interpolate UI elements:
f"Value: {slider}" - For complex objects:
f"Plot: {mo.as_html(fig)}"
UI Components (mo.ui.*)
Basic Inputs
slider = mo.ui.slider(0, 100, value=50, label="Value") number = mo.ui.number(0, 100, value=50) text = mo.ui.text(value="", placeholder="Enter text") checkbox = mo.ui.checkbox(value=False, label="Enable") dropdown = mo.ui.dropdown(["a", "b", "c"], value="a") radio = mo.ui.radio(["option1", "option2"], value="option1") multiselect = mo.ui.multiselect(["a", "b", "c"])
Buttons
button = mo.ui.button(label="Click") run_button = mo.ui.run_button(label="Run") # For triggering computation
Data Components
table = mo.ui.table(df) # Interactive table with selection dataframe = mo.ui.dataframe(df) # Editable dataframe data_explorer = mo.ui.data_explorer(df) # No-code exploration
Grouping UI Elements
# Forms (require submit button) form = mo.ui.text().form() # Batch in markdown form = mo.md(""" **Name**: {name} **Age**: {age} """).batch( name=mo.ui.text(), age=mo.ui.number(0, 120) ).form()
See references/ui_components.md for complete reference.
Layout Functions
# Stacking mo.hstack([el1, el2, el3], justify="center", gap=2) mo.vstack([el1, el2, el3], align="start", gap=1) # Containers mo.accordion({"Section 1": content1, "Section 2": content2}) mo.tabs({"Tab 1": content1, "Tab 2": content2}) mo.callout(content, kind="info") # info, warn, success, danger, neutral mo.sidebar([nav_content]) # Display mo.tree({"a": {"b": 1}}) # Tree view mo.stat(value="42", label="Users", caption="+5%") mo.lazy(expensive_component) # Defer until visible
Output Functions
mo.output.replace(new_content) # Replace cell output mo.output.append(additional) # Append to output mo.output.clear() # Clear output with mo.redirect_stdout(): print("This goes to cell output")
Status Indicators
# Progress bar for item in mo.status.progress_bar(items, title="Processing"): process(item) # Spinner with mo.status.spinner(title="Loading..."): load_data()
Control Flow
# Stop execution conditionally mo.stop(condition, mo.md("*Message when stopped*")) # Example: require button click run_button = mo.ui.run_button() mo.stop(not run_button.value, mo.md("Click Run to execute")) expensive_computation()
Caching
# In-memory cache (session only) @mo.cache def expensive_function(x, y): return compute(x, y) # Persistent cache (survives restarts) @mo.persistent_cache(name="embeddings") def compute_embeddings(text): return model.encode(text)
See references/caching.md for model output caching patterns.
State Management
Warning: Use sparingly—over 99% of cases don't need
mo.state().
@app.cell def _(mo): get_count, set_count = mo.state(0) return get_count, set_count @app.cell def _(mo, get_count, set_count): mo.ui.button( label=f"Count: {get_count()}", on_click=lambda _: set_count(lambda n: n + 1) ) return
Use only when maintaining history, synchronizing UI bidirectionally, or introducing cycles.
Interactive Plotting
# Altair with selection chart = alt.Chart(df).mark_point().encode(x="x", y="y") selection = mo.ui.altair_chart(chart, chart_selection="point") selected_data = selection.value # DataFrame of selected points # Plotly fig = px.scatter(df, x="x", y="y") interactive = mo.ui.plotly(fig) interactive.value # Selected points # Matplotlib interactive fig, ax = plt.subplots() ax.plot(x, y) mo.mpl.interactive(fig)
Supported: Matplotlib, Seaborn, Plotly, Altair, Bokeh, HoloViews, hvPlot
Wigglystuff Widgets
from wigglystuff import Slider2D, Paint, SortableList, Matrix, CellTour import marimo as mo slider2d = mo.ui.anywidget(Slider2D()) slider2d.x, slider2d.y paint = mo.ui.anywidget(Paint(width=400, height=300)) paint.to_pil() tour = mo.ui.anywidget(CellTour( steps=[ {"cell_name": "intro", "title": "Welcome", "description": "..."}, {"cell_name": "model", "title": "Model", "description": "..."}, ], auto_start=False ))
See references/wigglystuff.md for all widgets.
Best Practices
- Wrap reusable code in functions - Keeps intermediate variables local
- Use meaningful, unique variable names - e.g.,
,model1_sigmamodel2_sigma - Don't mutate across cells - Mutate in same cell or create new variables
- Write idempotent cells - Same inputs produce same outputs
- Use mo.stop() - Gate expensive operations behind conditions/buttons
- Use lazy loading -
for expensive components in tabs/accordionsmo.lazy() - Cache expensive ops -
for session,@mo.cache
for disk@mo.persistent_cache
CRITICAL: Pre-Edit Checklist
BEFORE making ANY edit to a marimo notebook:
- Read the current file state — The file may have been modified by marimo's editor
- Run
— Verify valid before and after editsmarimo check notebook.py
AFTER completing edits:
- Grep for
— Replace ALL print statements with marimo output (print(
,mo.md()
,mo.stat()
)mo.callout() - Run
— Verify no errors introducedmarimo check notebook.py
Common Gotchas
Output & Display
-
NEVER use print() in cells: Print statements do NOT display in run mode. Always use:
— formatted textmo.md(f"**Label:** {value}")
— metric cardsmo.stat(value=f"{x}", label="Label")
— callout boxesmo.callout(content, kind="info")- Return the dataframe/object directly — automatic display
-
mo.show_code() must be called as a statement, not in return
-
Never delete output without replacing: When removing print statements, replace with equivalent marimo output
Reactivity & Variables
- Closures in loops: Use default args
notlambda v, i=i: ...lambda v: ... i - on_change handlers: Only work if element is bound to global variable
- Dynamic UI elements: Must wrap in
,mo.ui.array()
, ormo.ui.dictionary()mo.ui.batch() - Type annotations: Registered as references unless quoted:
x: "SomeType"
Libraries
- matplotlib cut-off: Call
before outputtingplt.tight_layout() - dotenv: Use
dotenv.load_dotenv(dotenv.find_dotenv(usecwd=True))
References
- UI components: references/ui_components.md
- Wigglystuff widgets: references/wigglystuff.md
- Caching patterns: references/caching.md
- Advanced features: references/advanced.md (SQL, deployment, testing, routing)
- Official docs: https://docs.marimo.io