DSL: Attributes & events
phaze:attr — reactive attributes
Section titled “phaze:attr — reactive attributes”<article phaze:class={isFav() ? 'card fav' : 'card'} phaze:aria-label={`Card for ${name()}`} phaze:disabled={qty() <= 0}/>Transformed via phaze-compiler
<article class={() => isFav() ? 'card fav' : 'card'} aria-label={() => `Card for ${name()}`} disabled={() => qty() <= 0}/>The runtime detects the function value and wires a reactive binding. When tracked signals change, the binding re-runs the thunk and writes the result.
phaze: is only available on attributes — for reactive children, use phaze(). The two grammatical positions cover all reactive-expression cases:
<input phaze:value={name()} /> {/* attribute */}<p>{phaze(`Hello, ${name()}!`)}</p> {/* child */}on:event — native DOM events
Section titled “on:event — native DOM events”on:click is transformed by the phaze-compiler to onClick. The convention is sugar — Phaze’s JSX runtime accepts both forms — but on: matches Solid/Vue and reads more clearly when you also have phaze: and use: on the same element.
<button on:click={fn}>Send</button>Transformed via phaze-compiler
<button onClick={fn}>Send</button>CallExpression auto-thunk
Section titled “CallExpression auto-thunk”Bare call expressions as handler values are auto-thunked at compile time, so the () => ceremony disappears at every event-handler call site:
<button on:click={step.set('email')}>Email</button>Transformed via phaze-compiler
<button onClick={() => step.set('email')}>Email</button>The rule is shape-gated: only CallExpression values are wrapped. Function references, arrows, and method references pass through unchanged:
<button on:click={resend}>…</button> {/* Identifier → pass through */}<button on:click={(e) => doVerify(e)}>…</button> {/* Arrow → pass through */}<button on:click={obj.method}>…</button> {/* MemberExpression → pass through */}<button on:click={step.set('email')}>…</button> {/* CallExpression → auto-thunked */}Consistent with c / watch / phaze auto-thunking and use:spring’s auto-fuse: phaze-compile writes the boilerplate that’s identical at every call site.
A fuller example — hover + click on a state-machine signal
Section titled “A fuller example — hover + click on a state-machine signal”import { s } from '@madenowhere/phaze/match'import { spring } from '@madenowhere/photon/phaze' // use:spring
export default function HoverableDot() { const state = s<'rest' | 'hover' | 'pressed'>('rest')
const CARD_ANIM = { rest: { y: 0, opacity: 1 }, hover: { y: -4, opacity: 0.7 }, pressed: { y: 6, opacity: 0.9 }, springs: { y: { stiffness: 500, damping: 100, mass: 3 }, opacity: { stiffness: 300, damping: 60 }, }, }
return ( <div use:spring={CARD_ANIM[state()]} on:mouseenter={state.set('hover')} {/* CallExpression → auto-thunked */} on:mouseleave={state.set('rest')} {/* CallExpression → auto-thunked */} on:mousedown={state.set('pressed')} {/* CallExpression → auto-thunked */} on:mouseup={state.set('hover')} {/* CallExpression → auto-thunked */} class="size-3 rounded-full bg-black cursor-pointer" /> )}Every event handler is one expression. No () => boilerplate. The four on: lines read top-to-bottom as a natural state-transition table — exactly the four edges of the state machine.
Each state.set(...) is a CallExpression, so phaze-compile wraps it in () => state.set(...) at build time; the runtime sees a normal onMouseenter={fn} JSX attribute and wires it via the standard addEventListener path. use:spring={CARD_ANIM[state()]} picks up the per-channel physics from the nested springs key in the same record (auto-fuse — see the use:spring directive contract below). The whole interaction lives in one record + one component body.