SKILL.md
$2a
@app.cell
def _():
import marimo as mo
import numpy as np
return mo, np
@app.cell
def _():
print("hello world")
return
@app.cell
def _(np, slider):
np.array([1,2,3]) + slider.value
return
@app.cell
def _(mo):
slider = mo.ui.slider(1, 10, 1, label="number to add")
slider
return (slider,)
@app.cell
def _():
return
if name == "main":
app.run()
Notice how the notebook is structured with functions can represent cell contents. Each cell is defined with the `@app.cell` decorator and the inputs/outputs of the function are the inputs/outputs of the cell. marimo usually takes care of the dependencies between cells automatically.
## Running Marimo Notebooks
Run as script (non-interactive, for testing)
uv run <notebook.py>
Run interactively in browser
uv run marimo run <notebook.py>
Edit interactively
uv run marimo edit <notebook.py>
## Script Mode Detection
Use `mo.app_meta().mode == "script"` to detect CLI vs interactive:
@app.cell
def _(mo):
is_script_mode = mo.app_meta().mode == "script"
return (is_script_mode,)
## Key Principle: Keep It Simple
**Show all UI elements always.** Only change the data source in script mode.
- Sliders, buttons, widgets should always be created and displayed
- In script mode, just use synthetic/default data instead of waiting for user input
- Don't wrap everything in `if not is_script_mode` conditionals
- Don't use try/except for normal control flow
### Good Pattern
Always show the widget
@app.cell
def _(ScatterWidget, mo):
scatter_widget = mo.ui.anywidget(ScatterWidget())
scatter_widget
return (scatter_widget,)
Only change data source based on mode
@app.cell
def _(is_script_mode, make_moons, scatter_widget, np, torch):
if is_script_mode:
# Use synthetic data for testing
X, y = make_moons(n_samples=200, noise=0.2)
X_data = torch.tensor(X, dtype=torch.float32)
y_data = torch.tensor(y)
data_error = None
else:
# Use widget data in interactive mode
X, y = scatter_widget.widget.data_as_X_y
# ... process data ...
return X_data, y_data, data_error
Always show sliders - use their .value in both modes
@app.cell
def _(mo):
lr_slider = mo.ui.slider(start=0.001, stop=0.1, value=0.01)
lr_slider
return (lr_slider,)
Auto-run in script mode, wait for button in interactive
@app.cell
def _(is_script_mode, train_button, lr_slider, run_training, X_data, y_data):
if is_script_mode:
# Auto-run with slider defaults
results = run_training(X_data, y_data, lr=lr_slider.value)
else:
# Wait for button click
if train_button.value:
results = run_training(X_data, y_data, lr=lr_slider.value)
return (results,)
## State and Reactivity
Variables between cells define the reactivity of the notebook for 99% of the use-cases out there. No special state management needed. Don't mutate objects across cells (e.g., `my_list.append()`); create new objects instead. Avoid `mo.state()` unless you need bidirectional UI sync or accumulated callback state. See [STATE.md](https://github.com/marimo-team/skills/blob/HEAD/skills/marimo-notebook/references/STATE.md) for details.
## Don't Guard Cells with if Statements
Marimo's reactivity means cells only run when their dependencies are ready. Don't add unnecessary guards:
BAD - the if statement prevents the chart from showing
@app.cell
def _(plt, training_results):
if training_results: # WRONG - don't do this
fig, ax = plt.subplots()
ax.plot(training_results['losses'])
fig
return
GOOD - let marimo handle the dependency
@app.cell
def _(plt, training_results):
fig, ax = plt.subplots()
ax.plot(training_results['losses'])
fig
return
The cell won't run until `training_results` has a value anyway.
## Don't Use try/except for Control Flow
Don't wrap code in try/except blocks unless you're handling a specific, expected exception. Let errors surface naturally.
BAD - hiding errors behind try/except
@app.cell
def _(scatter_widget, np, torch):
try:
X, y = scatter_widget.widget.data_as_X_y
X = np.array(X, dtype=np.float32)
# ...
except Exception as e:
return None, None, f"Error: {e}"
GOOD - let it fail if something is wrong
@app.cell
def _(scatter_widget, np, torch):
X, y = scatter_widget.widget.data_as_X_y
X = np.array(X, dtype=np.float32)
# ...
Only use try/except when:
- You're handling a specific, known exception type
- The exception is expected in normal operation (e.g., file not found)
- You have a meaningful recovery action
## Cell Output Rendering
Marimo only renders the **final expression** of a cell. Indented or conditional expressions won't render:
BAD - indented expression won't render
@app.cell
def _(mo, condition):
if condition:
mo.md("This won't show!") # WRONG - indented
return
GOOD - final expression renders
@app.cell
def _(mo, condition):
result = mo.md("Shown!") if condition else mo.md("Also shown!")
result # This renders because it's the final expression
return
## PEP 723 Dependencies
Notebooks created via `marimo edit --sandbox` have these dependencies added to the top of the file automatically but it is a good practice to make sure these exist when creating a notebook too:
/// script
requires-python = ">=3.12"
dependencies = [
"marimo",
"torch>=2.0.0",
]
///
## marimo check
When working on a notebook it is important to check if the notebook can run. That's why marimo provides a `check` command that acts as a linter to find common mistakes.
uvx marimo check <notebook.py>
Make sure these are checked before handing a notebook back to the user.
**Important**: you have a tendency to over-do variables with an underscore prefix. You should only apply this to one or two variables at most. Consider creating a new variable instead of prefixing entire cells in marimo.
## api docs
If the user specifically wants you to use a marimo function, you can locally check the docs via:
uv --with marimo run python -c "import marimo as mo; help(mo.ui.form)"
## tests
By default, marimo discovers and executes tests inside your notebook.
When the optional `pytest` dependency is present, marimo runs `pytest` on cells that
consist exclusively of test code - i.e. functions whose names start with `test_`.
If the user asks you to add tests, make sure to add the `pytest` dependency is added and that
there is a cell that contains only test code.
For more information on testing with pytest see [PYTEST.md](https://github.com/marimo-team/skills/blob/HEAD/skills/marimo-notebook/references/PYTEST.md)
Once tests are added, you can run pytest from the commandline on the notebook to run pytest.
pytest <notebook.py>