Use when creating or editing marimo reactive Python notebooks. Covers cell structure, UI elements (mo.ui), reactivity, SQL integration, markdown, and deployment as apps. Triggers on .py notebook creation, marimo edit/run commands, reactive notebook development, or interactive data exploration. Extends python-development skill.
/plugin marketplace add btimothy-har/mycc-config/plugin install brian-claude-skills@brian-claudeThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Extends the python-development skill. Marimo is a reactive Python notebook—stored as pure .py files, git-friendly, executable as scripts, and deployable as apps.
Reactivity
Cell Rules
_ prefix for private variables - Variables starting with _ are cell-localData Flow
.value - All UI elements have a .value attributeAdd to pyproject.toml:
[project]
dependencies = [
"marimo",
]
# For SQL support
[project.optional-dependencies]
sql = ["marimo[sql]"]
uv run marimo edit notebook.py # Open/create notebook in editor
uv run marimo run notebook.py # Run as app (code hidden)
uv run marimo edit --headless # Run without opening browser
uv run marimo tutorial intro # Run interactive tutorial
uv run marimo convert notebook.ipynb > notebook.py # Convert from Jupyter
Marimo notebooks are pure Python files:
import marimo
app = marimo.App()
@app.cell
def _():
import marimo as mo
import pandas as pd
return mo, pd
@app.cell
def _(mo):
slider = mo.ui.slider(1, 100, value=50, label="Count")
slider
return slider,
@app.cell
def _(slider, pd):
# This cell re-runs when slider changes
df = pd.DataFrame({"values": range(slider.value)})
df
return df,
if __name__ == "__main__":
app.run()
import marimo as mo
# Slider
slider = mo.ui.slider(start=0, stop=100, value=50, step=1, label="Value")
# Number input
number = mo.ui.number(start=0, stop=100, value=10, label="Count")
# Text input
text = mo.ui.text(value="", label="Name", placeholder="Enter name")
# Text area
textarea = mo.ui.text_area(value="", label="Description")
# Checkbox
checkbox = mo.ui.checkbox(label="Enable feature", value=False)
# Dropdown
dropdown = mo.ui.dropdown(
options=["Option A", "Option B", "Option C"],
value="Option A",
label="Choose one",
)
# Dropdown with key-value pairs
dropdown_kv = mo.ui.dropdown(
options={"Label 1": "value1", "Label 2": "value2"},
value="Label 1",
label="Select",
)
# Radio buttons
radio = mo.ui.radio(
options=["Small", "Medium", "Large"],
value="Medium",
label="Size",
)
# Multiselect
multiselect = mo.ui.multiselect(
options=["A", "B", "C", "D"],
label="Select multiple",
)
# Date picker
date = mo.ui.date(label="Select date")
# File upload
file = mo.ui.file(filetypes=[".csv", ".json"], label="Upload file")
# Button
button = mo.ui.button(label="Click me", kind="primary")
# Run button (triggers cell re-run)
run_button = mo.ui.run_button(label="Run analysis")
@app.cell
def _(slider, dropdown, checkbox):
# Access current values
count = slider.value # int
choice = dropdown.value # str
enabled = checkbox.value # bool
mo.md(f"Count: {count}, Choice: {choice}, Enabled: {enabled}")
# Interactive dataframe viewer
mo.ui.dataframe(df)
# Data explorer with visualizations
mo.ui.data_explorer(df)
# Interactive table with selection
table = mo.ui.table(
data=df,
selection="multi", # or "single"
pagination=True,
)
# Access selected rows: table.value
import marimo as mo
# Basic markdown
mo.md("# Hello World")
# Markdown with Python values
name = "marimo"
mo.md(f"Welcome to **{name}**!")
# Embed UI elements in markdown
slider = mo.ui.slider(1, 10, label="Value")
mo.md(f"Choose a value: {slider}")
# LaTeX support
mo.md(r"The formula is $E = mc^2$")
# Multi-line with f-string
mo.md(f"""
# Analysis Results
- **Count**: {slider.value}
- **Status**: {"Active" if slider.value > 5 else "Inactive"}
""")
import marimo as mo
# Horizontal stack
mo.hstack([element1, element2, element3], justify="center", gap=2)
# Vertical stack
mo.vstack([element1, element2], align="start", gap=1)
# Tabs
mo.ui.tabs({
"Tab 1": content1,
"Tab 2": content2,
"Tab 3": content3,
})
# Accordion
mo.accordion({
"Section 1": content1,
"Section 2": content2,
})
# Callout boxes
mo.callout("Important message", kind="info") # info, warn, danger, success
# Grid layout
mo.ui.batch(
mo.md("""
| Setting | Value |
|---------|-------|
| Name | {name} |
| Count | {count} |
"""),
name=mo.ui.text(),
count=mo.ui.number(start=0, stop=100),
)
Group UI elements and submit together:
# Create a form that submits on button click
form = mo.md("""
**Configuration**
- Epsilon: {epsilon}
- Delta: {delta}
- Name: {name}
""").batch(
epsilon=mo.ui.slider(0.1, 1.0, step=0.1, value=0.5),
delta=mo.ui.number(start=1, stop=10, value=5),
name=mo.ui.text(value="default"),
).form(submit_button_label="Apply")
# Display the form
form
# Access submitted values (None until submitted)
if form.value:
epsilon = form.value["epsilon"]
delta = form.value["delta"]
import marimo as mo
# SQL cells return dataframes
df = mo.sql(f"""
SELECT *
FROM my_table
WHERE count > {slider.value}
LIMIT 100
""")
# Use with DuckDB (auto-detected)
import duckdb
conn = duckdb.connect("data.db")
# Query Python dataframes directly
df = mo.sql(f"""
SELECT * FROM df_source
WHERE category = '{dropdown.value}'
""")
import marimo as mo
import altair as alt
# Altair charts are automatically interactive
chart = alt.Chart(df).mark_point().encode(
x="x:Q",
y="y:Q",
color="category:N",
)
chart
# Make chart selectable
selectable_chart = mo.ui.altair_chart(chart)
# Access selected points: selectable_chart.value
import marimo as mo
@mo.cache
def expensive_computation(x: int) -> int:
# Result is cached based on input
return x ** 2
# Or cache to disk
@mo.cache(pin=True)
def load_large_dataset(path: str):
return pd.read_parquet(path)
# Run as script (cells execute in topological order)
uv run python notebook.py
# Run as interactive app (code hidden)
uv run marimo run notebook.py
# Run with CLI args
uv run marimo run notebook.py -- --param1 value1
For expensive notebooks, mark cells as stale instead of auto-running:
# In notebook settings or via CLI
uv run marimo edit notebook.py --lazy
@app.cell
def _(checkbox, data):
if checkbox.value:
result = mo.md(f"Data: {data}")
else:
result = mo.md("Enable checkbox to see data")
result
@app.cell
def _(count_slider):
# Create dynamic number of inputs
inputs = mo.ui.array([
mo.ui.text(label=f"Item {i}")
for i in range(count_slider.value)
])
inputs
@app.cell
def _(inputs):
# Access all values
values = inputs.value # List of strings
mo.md(f"Values: {values}")
@app.cell
def _(data):
# Stop cell execution if condition not met
mo.stop(data is None, mo.md("**Waiting for data...**"))
# Continue processing
result = process(data)
result
_ prefix for cell-local variables - _temp = compute() won't leak_ prefix for reactivity