Interact — controls

Declare interactive controls inside an ```interact block — slider / timer / input / chip / select / toggle. 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 MD.


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)
  • Auto-playing animation (drive a scene or chart with a timer instead of manual dragging)
  • 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 range=0..100 step=1 default=50
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 MD provides 6 control types:

Control Use Syntax
slider Numeric slider slider name range=A..B step=S default=V label="..."
timer Auto-playing numeric driver timer name range=A..B step=S speed=FPS loop=true|false
input Text input input name default="..." placeholder="..." label="..."
chip Segmented choice chip name options=a,b,c default=a label="..."
select Dropdown select name options=a,b,c default=a label="..."
toggle Boolean switch toggle name default=true label="..."

All attributes are key=value pairs — there is no positional-argument form for any control.


Each control in detail

slider

slider <name> range=<A>..<B> step=<S> default=<V> label="..."
Attribute Type Purpose
name identifier Variable name (referenced as {name} in template)
range A..B Minimum and maximum, joined by ..
step number Step size (per-drag increment)
default number Initial value (must be in [A, B])
label string Optional display label
slider rate range=0..0.2 step=0.01 default=0.05 label="Rate"        ← 0%-20%, 1% step
slider weight range=40..200 step=0.5 default=70 label="Weight (kg)"
slider temperature range=-20..40 step=1 default=20 label="Temp (°C)"

timer

timer <name> range=<A>..<B> step=<S> speed=<FPS> loop=true|false
Attribute Type Purpose
name identifier Variable name (referenced as {name} in template)
range A..B Minimum and maximum the timer sweeps through
step number Increment per tick
speed number Ticks per second (FPS)
loop bool true wraps around at B and repeats; false runs through a single pass and stops at B
timer t range=0..10 step=0.1 speed=30 loop=true      ← loops forever
timer intro range=0..1 step=0.02 speed=60 loop=false  ← single pass, 0 → 1

There is no separate mode for pausing, playing a single pass, or reversing direction — loop=false is the only variant, and it is how you get a one-shot animation.

input

input <name> default="..." placeholder="..." label="..."
Attribute Type Purpose
name identifier Variable name
default string Default content
placeholder string Optional placeholder text (shown when empty)
label string Optional display label
input email default="you@example.com" label="Email"
input city default="Shanghai" label="City"
input apiKey default="" placeholder="sk-..." label="API key"     ← Empty default

input accepts arbitrary text — pure string, no format validation. For numeric input, use slider instead.

chip

chip <name> options=<a,b,c> default=<a> label="..."
Attribute Type Purpose
name identifier Variable name
options comma-separated list Choices, shown as segmented chips
default string Which option is initially selected (must be one of options)
label string Optional display label
chip size options=S,M,L,XL default=M label="Size"
chip theme options=dark,light,system default=dark label="Theme"

select

select <name> options=<a,b,c> default=<a> label="..."
Attribute Type Purpose
name identifier Variable name
options comma-separated list Options; a dropdown
default string Initially selected option (must be one of options)
label string Optional display label
select theme options=dark,light,system default=dark label="Theme"
select unit options=metric,imperial default=metric label="Units"
select region options=us-east,us-west,eu-west,ap-northeast default=us-east label="Region"

chip and select carry the same data shape (one string out of a fixed option list) — pick whichever reads better in the layout; chip suits 2-4 short options, select suits longer lists.

toggle

toggle <name> default=true|false label="..."
Attribute Type Purpose
name identifier Variable name
default bool Initial state
label string Optional display label
toggle darkMode default=false label="Dark mode"
toggle notifications default=true label="Notifications"
toggle showAdvanced default=false label="Show advanced"

toggle's value is the string "true" / "false", not a real boolean. For conditional display, compare it explicitly: {darkMode == 'true' ? 'Dark' : 'Light'}.


Examples

Example 1: simplest slider

```interact
slider x range=0..10 step=1 default=5
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 range=40..120 step=1 default=70 label="Weight"
input name default="Alice" label="Name"
select activity options=sedentary,moderate,active default=moderate label="Activity"
toggle metric default=true label="Metric units"
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: chip-driven step counter

```interact
chip step options=1,2,3,5,10 default=1 label="Step size"
slider count range=0..100 step=1 default=0 label="Count"
computed next = count + step
template stl:
[Count] -> [{count}]
[Next tick (+{step})] -> [{next}]
```

Renders: a chip to pick the step size + a slider for the current count; one line previews what the count would be after adding the chosen step.

The 6-control set has no discrete click-trigger widget — every control is a continuous input (drag / type / pick). Where older docs showed a click-triggered "+1 counter" pattern, drive the same idea with a slider or chip instead, as above.

Example 4: select + toggle for display mode switch

```interact
select unit options=metric,imperial default=metric label="Unit"
slider weight range=40..120 step=1 default=70 label="Weight"
computed display = unit == 'metric' ? weight : weight * 2.20462
computed unitLabel = unit == 'metric' ? 'kg' : 'lb'
template stl:
[Weight] -> [{round(display, 1)} {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. There are no format specifiers either — fixed-decimal output goes through round(expr, N), as shown above.


Plain-text fallback behavior

```interact is a fenced code block with interact lang. In readers without support:

```interact
slider x range=0..10 step=1 default=5
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 range/default/step attributes).

Per-reader breakdown:

Reader Render
Rho MD 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. Positional arguments instead of attributes

slider x <min> <max> <default> <step>     ← ❌ positional form is not parsed
slider x range=100..0 step=1 default=50   ← ❌ min > max, undefined behavior
slider x range=0..100 step=1 default=1000 ← ❌ default > max
slider x range=0..100 step=0 default=50   ← ❌ step=0, can't drag
slider x range=0..100 step=1 default=50   ← ✅

Every control takes key=value attributes, not positional min/max/initial/step. range is min ≤ default ≤ max; step > 0.

2. Variable name with spaces / special chars

slider my var range=0..10 step=1 default=5     ← ❌ breaks parsing
slider my-var range=0..10 step=1 default=5     ← ⚠️ hyphens rejected
slider my_var range=0..10 step=1 default=5     ← ✅
slider myVar range=0..10 step=1 default=5      ← ✅

Variable names: only [A-Za-z0-9_], like standard programming language identifiers.

3. Using undeclared variables in template

```interact
slider x range=0..10 step=1 default=5
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/timer/input/chip/select/toggle/computed.

4. chip / select options with special chars

select theme options=dark,light default=dark          ← ✅
chip size options=S,M,L,XL default=M                  ← ✅
select theme options="dark mode,light mode" default="dark mode"   ← ⚠️ multi-word options need care with commas

Options are a bare comma-separated list — don't wrap the whole list in quotes; avoid commas inside individual option values.

5. toggle compared as a boolean instead of a string

computed label = darkMode ? "Dark" : "Light"            ← ❌ darkMode is the string "true"/"false", not a real bool
computed label = darkMode == 'true' ? 'Dark' : 'Light'   ← ✅

toggle's value is always the string "true" or "false" — compare it explicitly with == 'true'.

6. input takes a number but treated as string

```interact
input age default="30" label="Age"
template stl:
[Age+1] -> [{age + 1}]    ← ❌ "30" + 1 is not numeric addition — input is a string
```

→ input value is always a string, meant for free text substituted verbatim into the template. For numeric values, use slider (or timer) instead of input.

7. Too many controls

```interact
slider a range=0..10 step=1 default=5
slider b range=0..10 step=1 default=5
slider c range=0..10 step=1 default=5
slider d range=0..10 step=1 default=5
slider e range=0..10 step=1 default=5
slider f range=0..10 step=1 default=5
... (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

Open in Rho MD →