Interact — controls

Declare interactive controls inside an ```interact block — 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 slider or 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