Interact — text template

Inside an ```interact block, declare a plain-text output template with template stl: — embed control values into STL or any text and display them live. The "output side" of Interact controls — the most common and simplest template type.


When to use

  • Lightweight calculators (substitute slider/input values into a text result)
  • Configuration preview ("you selected = ..." live echo)
  • STL node dynamic generation (drag a slider, change a node's confidence / description)
  • Teaching formula echo (input → formula → result)
  • Need a chart → use Vega-Lite template
  • Need animation / SVG → use SVG template
  • Complex layout / multiple sections → split into multiple interact blocks or use Layout

Basic syntax

```interact
slider x range=0..10 step=1 default=5 label="x"
template stl:
[Selected value] -> [{x}]
```

template <type>: declares the template type + colon. Everything after the colon is template content. {var} is a placeholder.

Renders: a slider with a live STL line below: [Selected value] -> [5]. Drag → number changes.


Template types

Type Use Doc
template stl: Output STL statements / arbitrary plain text This page
template vega-lite: Output a live chart (Vega-Lite spec) Interact + Vega-Lite
template svg: Output an SVG scene SVG scenes

The "stl" type name is historical — it doesn't only output STL, it can output any text. The [A] -> [B] in the template is just text; it isn't actually parsed into STL nodes (unless you later feed the rendered output into STG).


Placeholder syntax {...}

Inside the template, { } wraps an expression that's evaluated live.

1. Direct variable reference

template stl:
[Weight] -> [{weight}]
[Name] -> [{name}]
[Active] -> [{enabled}]

2. Expressions

template stl:
[BMI] -> [{weight / (height * height)}]
[Discounted price] -> [{price * (1 - discount)}]

The mini DSL supports (see Computed for full details):

  • Arithmetic: + - * / % ** (also pow(a, b))
  • Comparison: == != < > <= >=
  • Logical: && || !
  • Ternary: cond ? a : b
  • Functions: sqrt(x) abs(x) min(...) max(...) sin(x) cos(x) tan(x) log(x) exp(x) floor(x) ceil(x) round(x[, d]) — full list in Computed
  • Constants: pi e tau inf nan

3. Number formatting

There is no format-spec syntax — no Python-style f-string suffix (colon + precision + type letter) after the expression inside {}. A placeholder holds exactly one expression — nothing after it.

Numbers auto-format on output:

  • Integers render plain: {5}5
  • Floats render at ~6 significant digits: {1/3}0.333333

For a fixed number of decimal places, call round(expr, N) — that's the only mechanism:

template stl:
[BMI] -> [{round(bmi, 1)}]                ← 1 decimal place
[Price] -> [${round(price, 2)}]           ← 2 decimals (money)
[Percent] -> [{round(rate * 100, 1)}%]    ← convert to percentage
[Int] -> [{round(count, 0)}]              ← force whole number

There is no scientific-notation or alignment/width formatting — only round(expr, N) (and, upstream of it, ordinary arithmetic).


Examples

Example 1: pure echo

```interact
slider x range=0..100 step=1 default=50 label="x"
template stl:
[Current value] -> [{x}]
```

Renders: slider + one line "Current value = 50"; live update on drag.

Example 2: multi-line STL output + rounding

```interact
slider weight range=40..120 step=1 default=70 label="Weight (kg)"
slider height range=1.4..2.1 step=0.01 default=1.7 label="Height (m)"
template stl:
[Weight] -> [{weight} kg]
[Height] -> [{round(height, 2)} m]
[BMI] -> [{round(weight / (height * height), 1)}] ::mod(category="{(weight / (height * height)) < 18.5 ? 'Underweight' : (weight / (height * height)) < 24 ? 'Normal' : (weight / (height * height)) < 28 ? 'Overweight' : 'Obese'}")
```

Renders: 3 lines of live STL output. Line 3's category uses nested ternaries (single-quoted string literals, since the whole placeholder already sits inside a double-quoted ::mod() value) to display BMI category. For repeated expressions, refactor to computed derived values.

Example 3: free text template (not STL)

```interact
input name default="Alice" label="Name"
slider age range=1..100 step=1 default=30 label="Age"
select role options=Student,Engineer,Teacher,Other default=Student label="Role"
template stl:
Hello, {name}!

You're a {age}-year-old **{role}**.

Next year you'll be {age + 1}.
```

Renders: a free-text template (not constrained to STL form). The "stl" type name is really "plain text."

Example 4: configuration preview

```interact
input projectName default="my-app" label="Project name"
select stack options=Next.js,Astro,SvelteKit default=Next.js label="Stack"
toggle typescript default=true label="TypeScript"
toggle eslint default=true label="ESLint"
template stl:
**Project config preview**:

```bash
npx create-{stack} {projectName}{typescript == 'true' ? " --typescript" : ""}{eslint == 'true' ? " --eslint" : ""}
```
```

Renders: form → live-assembled command line. toggle yields the string "true"/"false", so it's compared with == 'true' — the ternary appends flags conditionally.


Plain-text fallback behavior

```interact blocks render as code blocks in non-supporting readers. Template content preserves {var} literals — readers see the template source + control declarations, fully understanding the original document's intent.

```interact
slider x range=0..10 step=1 default=5 label="x"
template stl:
[Selected value] -> [{x}]
```

→ Plain-text reader sees the above (with {x} placeholder), knows "x is a dynamic value here."

Reader Render
Rho Full interaction + template live-evaluation
GitHub web Code block ({x} literal visible)
Obsidian Same
VS Code default preview Same
cat / less Plain text

Common pitfalls

1. Reference an undeclared variable

```interact
slider x range=0..10 step=1 default=5 label="x"
template stl:
[Result] -> [{y}]      ← ❌ y not declared
```

→ Template outputs {y} literal. All variables in template must first be declared via slider/input/select/toggle/chip/computed.

2. Expecting a format spec

template stl:
[BMI] -> [{round(bmi, 1)}]  ← ✅ 1 decimal, via round()

A tutorial or LLM used to writing Python f-strings will reflexively add a colon-plus-precision suffix after the expression (mirroring Python's own format-spec mini-language). That suffix has no meaning here — the parser expects the placeholder to contain nothing but the expression, so anything appended after it breaks parsing.

There is no format-spec character set (f/e/d/s/% suffixes don't exist). Precision is controlled entirely by round(expr, N) upstream of the placeholder.

3. Expressions with unescaped nested quotes

template stl:
[Label] -> [{name == "Alice" ? "Admin" : "User"}]    ← ⚠️ works stand-alone, but breaks the moment the template itself is inside a double-quoted STL/JSON value

→ When a placeholder sits inside something that's already double-quoted (an ::mod(key="...") value, a vega-lite JSON string, an SVG attribute), the template's own " closes that outer quote early. Fix: use single-quoted string literals inside the expression — {name == 'Alice' ? 'Admin' : 'User'} — or pre-compute in computed and reference the computed name.

4. Deep nesting with callout / layout

:::card
```interact
slider x range=0..10 step=1 default=5 label="x"
template stl:
[Selected] -> [{x}]
```
:::

✅ Legal. But the interact block's height changes with template output length. Keep template output line count stable or give the container a fixed min-height.

5. Template output contains markdown special chars

template stl:
[Selected] -> [{x}]      ← `[` `]` are markdown link syntax

In practice, usually fine — Rho renders template output as a fenced code block result, not triggering markdown link parsing. But watch out for * _ and other emphasis chars in template output.

6. Trying to chart with stl template

template stl:
draw a bar chart: {...}      ← ❌ stl template doesn't render charts

→ stl template outputs plain text only, no graphical rendering. For charts use template vega-lite: (see Interact + Vega-Lite).

7. Template output expects markdown rendering

template stl:
**bold** and *italic*     ← ❌ These chars output as literals, not bold/italic

→ Template defaults to plain-text output, no further markdown rendering. Stick to plain text for safety.


See also

Open in Rho MD →