Interact — controls
Declare interactive controls inside an
```interactblock — slider / input / select / toggle / button. Readers manipulate the controls; the controls drive live output in the same or other blocks. This is the core engine of all dynamic content in Rho.
When to use
- ✅ Parameterized display (financial calculators / physics simulations / color palette pickers / API call sandboxes)
- ✅ Visualizing teaching formulas (drag a slider, see how the formula changes)
- ✅ Lightweight calculators (BMI / compound interest / unit conversion / mortgage)
- ✅ Data exploration (slider tweaks → chart redraws — pair with Vega-Lite chart)
- ❌ Real form submission (state lives only in reader memory; not persisted)
- ❌ Complex business logic (the mini DSL isn't JS — no
if/else/loop) - ❌ Cross-page state sharing (state is per-page only)
Basic syntax
An interact block has two parts: control declarations + template output.
\```interact
slider x 0 100 50 1
template stl:
[Current value] -> [{x}]
\```
Renders: a slider (min 0, max 100, initial 50, step 1) + a live line "Current value = 50". Drag → number changes.
All control types
Rho provides 5 control types:
| Control | Use | Syntax |
|---|---|---|
| slider | Numeric slider | slider name min max initial step |
| input | Text input | input name "default text" |
| select | Dropdown | select name "opt1" "opt2" "opt3" |
| toggle | Boolean switch | `toggle name <true |
| button | Button (click triggers value change) | button name "label" <value> |
Each control in detail
slider
slider <name> <min> <max> <initial> <step>
| Param | Type | Purpose |
|---|---|---|
name |
identifier | Variable name (referenced as {name} in template) |
min |
number | Minimum |
max |
number | Maximum |
initial |
number | Initial value (must be in [min, max]) |
step |
number | Step size (per-drag increment) |
slider rate 0 0.2 0.05 0.01 ← rate 0%-20%, 1% step
slider weight 40 200 70 0.5 ← weight 40-200 kg, 0.5 kg precision
slider temperature -20 40 20 1 ← temperature -20°C to 40°C, 1°C precision
input
input <name> "<default text>"
| Param | Type | Purpose |
|---|---|---|
name |
identifier | Variable name |
default |
string | Default content |
input email "[email protected]"
input city "Shanghai"
input apiKey "" ← Empty default
input accepts arbitrary text — pure string, no format validation. For numeric input, use
slideror add a doc-level note asking the user to ensure format.
select
select <name> "<opt1>" "<opt2>" "<opt3>" ...
| Param | Type | Purpose |
|---|---|---|
name |
identifier | Variable name |
opt* |
string | Options; first is the default |
select theme "dark" "light" "system"
select size "S" "M" "L" "XL"
select region "us-east" "us-west" "eu-west" "ap-northeast"
toggle
toggle <name> <true|false>
| Param | Type | Purpose |
|---|---|---|
name |
identifier | Variable name |
default |
bool | Initial state |
toggle darkMode false
toggle notifications true
toggle showAdvanced false
toggle expands in template as
true/false(string). For conditional display, pair with computed ternaries or multiple template blocks.
button
button <name> "<label>" <value>
| Param | Type | Purpose |
|---|---|---|
name |
identifier | Variable name |
label |
string | Button text |
value |
any | Value assigned to name on click |
button reset "Reset" 0
button increment "+1" "{counter+1}" ← Pair with computed for a counter
button trigger "Start animation" "running"
button is unlike other controls — it only changes state on click, not continuously. Best for "reset", "trigger", "step" — discrete actions.
Examples
Example 1: simplest slider
\```interact
slider x 0 10 5 1
template stl:
[Selected value] -> [{x}]
\```
Renders: slider (0-10, initial 5), one line "Selected value = 5" below.
Example 2: multi-control combo (parameter panel)
\```interact
slider weight 40 120 70 1
input name "Alice"
select activity "sedentary" "moderate" "active"
toggle metric true
template stl:
[Name] -> [{name}]
[Weight] -> [{weight} kg]
[Activity] -> [{activity}]
[Metric units] -> [{metric}]
\```
Renders: 4 different control types (slider + text + dropdown + toggle) stacked at top; 4 live output lines below.
Example 3: button + computed counter
\```interact
button inc "+1" "{counter+1}"
button reset "Reset" 0
computed counter = 0 # initial
template stl:
[Count] -> [{counter}]
\```
Renders: two buttons + "Count = 0". Click +1 → "Count = 1", click again → "Count = 2", click Reset → "Count = 0".
Note: button's value uses
{counter+1}referencing the computed itself — that's the legal self-reference pattern, designed specifically for buttons.
Example 4: select + toggle for display mode switch
\```interact
select unit "metric" "imperial"
slider weight 40 120 70 1
computed display = unit == "metric" ? weight : weight * 2.20462
computed unitLabel = unit == "metric" ? "kg" : "lb"
template stl:
[Weight] -> [{display:.1f} {unitLabel}]
\```
Renders: dropdown selects metric/imperial + slider; one line shows 70 kg or 154.3 lb based on the unit.
The mini DSL supports the ternary
cond ? a : b, but not if/else statements.
Plain-text fallback behavior
```interact is a fenced code block with interact lang. In readers without support:
```interact
slider x 0 10 5 1
template stl:
[Selected value] -> [{x}]
→ shows as a **syntax-highlighted code block** — readers see all sliders / inputs / template declarations, **fully understand the original document's intent** (including min/max/initial parameters).
Per-reader breakdown:
| Reader | Render |
|---|---|
| **Rho** | Full interaction (controls + live template output) |
| **GitHub web** | Code block (slider/input/select literals + template literal all visible) |
| **Obsidian** | Same |
| **VS Code default preview** | Same |
| **cat / less** | Plain text |
**Information preserved** — readers see the parameter space + template structure; they just can't drag.
---
## Common pitfalls
### 1. Wrong slider parameter order
```markdown
slider x 100 0 50 1 ← ❌ min > max, undefined behavior
slider x 0 100 1000 1 ← ❌ initial > max
slider x 0 100 50 0 ← ❌ step=0, can't drag
slider x 0 100 50 1 ← ✅
Fixed order: min max initial step; min ≤ initial ≤ max; step > 0.
2. Variable name with spaces / special chars
slider my var 0 10 5 1 ← ❌ "var" parsed as max
slider my-var 0 10 5 1 ← ⚠️ Some parsers reject hyphens
slider my_var 0 10 5 1 ← ✅
slider myVar 0 10 5 1 ← ✅
Variable names: only [A-Za-z0-9_], like standard programming language identifiers.
3. Using undeclared variables in template
\```interact
slider x 0 10 5 1
template stl:
[Result] -> [{y}] ← ❌ y not declared
\```
→ Template renders {y} as literal {y} or throws "undefined variable" error. Variables in template must first be declared via slider/input/select/toggle/computed.
4. select options with special chars
select theme "dark" "light" ← ✅
select theme "dark mode" "light mode" ← ⚠️ Options with spaces must be quoted
select theme "with"quotes" ← ❌ Nested quotes break parsing
Options with spaces must be in double quotes; nested quotes use full-width "" or escape \".
5. button value referencing variable but unquoted
button inc "+1" {counter+1} ← ❌ value unquoted with {expr}, undefined
button inc "+1" "{counter+1}" ← ✅ value is a string literal containing an expression
button values containing expressions must be wrapped in double quotes.
6. input takes a number but treated as string
\```interact
input age "30"
template stl:
[Age+1] -> [{age + 1}] ← ❌ "30" + 1 = "301" (string concatenation)
\```
→ input value is always a string. To do math you must cast: use mini DSL parseFloat({age}) or use slider instead of input.
Best practice: numeric input → slider; free text → input.
7. Too many controls
\```interact
slider a 0 10 5 1
slider b 0 10 5 1
slider c 0 10 5 1
slider d 0 10 5 1
slider e 0 10 5 1
slider f 0 10 5 1
... (12 sliders)
template stl:
[Result] -> [{a + b + c + d + e + f + ...}]
\```
→ Above 6-7 controls, UI gets crowded and readers lose track. Improvements:
- Use Layout grid to group controls into cards
- Split into multiple interact blocks (share variables via Shared state namespace)
- Reconsider: do you really need this many controls?
Where to next
| Want to… | Entry |
|---|---|
| Learn template syntax | → Interact text template |
| Add derived computations (not just echo input) | → Computed |
| Add live charts (Vega-Lite) | → Interact + Vega-Lite |
| Share variables across multiple interact blocks | → Shared state (namespace) |
| Add auto-playing animation | → Timer |
| Draw SVG scenes | → SVG scenes |
See also
- Interact text template — Template syntax details
- Computed — Derived values
- Interact + Vega-Lite chart — Slider-driven live charts
- Timer — Auto-play (replaces manual dragging)
- Shared state (namespace)
- Plain-text fallback principle