Expressions

Dynamic values, bindings, conditionals, and event handlers. Expressions are how KERN nodes connect to runtime state.

Expression blocks {{ }}

Double curly braces embed JavaScript expressions into any prop value. The parser treats {{ ... }} as a single expression token.

state name=count initial={{ 0 }}
state name=items initial={{ [] }}
text value={{ count + " items" }}
text value={{ loading ? "Loading..." : "Ready" }}

Expressions can contain any valid JavaScript — ternaries, function calls, template literals, array methods.

Two-way binding: bind=

The bind= prop creates two-way data binding between an input and a state variable. The compiler auto-generates the setter.

state name=query initial=""
input bind=query placeholder="Search..."

Compiles to React:

const [query, setQuery] = useState('');
<input value={query} onChange={(e) => setQuery(e.target.value)}
  placeholder="Search..." />

bind= works on input, textarea, slider, and toggle nodes. Each generates the appropriate setter pattern for its input type.

Event handlers: onClick=

Button nodes accept onClick= with either a bare value or an expression block:

button text="Increment" onClick={{ () => setCount(c => c + 1) }}
button text="Reset" onClick={{ () => setCount(0) }}

When onClick= is present, the compiler marks the component as a client component (adds 'use client' in Next.js).

Event handlers: on event=

The on node defines event handlers that compile to useCallback + useEffect for global listeners in React, or onMounted/onUnmounted in Vue:

screen name=App
  on event=click
    handler <<<
      setCount(prev => prev + 1);
    >>>
  on event=key key=Enter
    handler <<<
      processInput(buffer);
    >>>
  on event=submit async=true
    handler <<<
      e.preventDefault();
      await saveForm(data);
    >>>

Supported events include: click, key, keydown, keyup, submit, change, focus, blur, and scroll. Each gets the correct React event type (React.MouseEvent, React.KeyboardEvent, etc.).

The key= prop on keyboard events adds an automatic guard:

// on event=key key=Enter compiles to:
const handleKey = useCallback((e: React.KeyboardEvent) => {
  if (e.key !== 'Enter') return;
  processInput(buffer);
}, []);

Conditional rendering: conditional if=

The conditional node renders its children only when the if= expression is truthy:

conditional if={{ !loading }}
  text value="Data loaded"
  list separator=true
    item name="First"
    item name="Second"

Compiles to JSX conditional rendering:

{!loading && (
  <>
    <span>Data loaded</span>
    ...
  </>
)}

State with expressions

The state node uses initial= for default values. Expression blocks allow non-string initializers:

state name=darkMode initial=false           ← bare value (string)
state name=count initial={{ 0 }}            ← expression (number)
state name=items initial={{ [] }}           ← expression (empty array)
state name=config initial={{ { key: 1 } }} ← expression (object)

Text with dynamic values

Text nodes render expressions directly into JSX:

text value="Static text"                           ← string literal
text value={{ count }}                              ← variable reference
text value={{ items.length + " results" }}          ← concatenation
text value={{ isActive ? "On" : "Off" }}            ← ternary
text bind=username                                  ← bound to state

The bind= prop on text nodes renders the state variable directly: {username}.