Code
Reactivity — @click · x-data · x-for
After the server-rendered page hydrates, state lives in the browser.
@click
handlers run in the nearest
x-data
scope; mutating that state re-renders the owning subtree — no server roundtrip. Every demo
below is live — try it.
@click counter
<div class="flex items-center gap-4" x-data="{ count: 0 }">
<button @click="count++" class="">Increment</button>
<span class="" x-text="count"></span>
</div>
Result
0
x-data toggle
<div x-data="{ open: false }">
<button @click="open = !open" class="">Toggle panel</button>
<div x-show="open" class="">Now you see me — x-show flipped by @click.</div>
</div>
Result
keyed x-for add / remove
<div x-data="{ items: [{ id: 1, label: 'alpha' }, { id: 2, label: 'beta' }], nextId: 3 }">
<div class="flex gap-2 mb-3">
<button @click="items.push({ id: nextId, label: 'item ' + nextId }); nextId++" class="">Add</button>
<button @click="items.pop()" class="">Remove last</button>
</div>
<ul class="space-y-1">
<li x-for="item in items" :key="item.id" class="" x-text="item.label"></li>
</ul>
</div>
Result
- alpha
- beta
$watch
$watch('form.email', cb)
runs cb
whenever a value changes. Give it a dotted path, or a function such as
() => items.length; a change deep inside an object or an array
counts.
$watch on a path and on a function
<div x-data="{ form: { email: '' }, items: [], log: [] }"
x-init="
$watch('form.email', v => log.push('path form.email → ' + v));
$watch(() => items.length, n => log.push('fn items.length → ' + n))
">
<div class="">
<button @click="form.email = 'a' + form.email.length + '@x.dev'" class="">Change form.email</button>
<button @click="items.push(items.length)" class="">Push item (in place)</button>
</div>
<ul class="">
<li x-for="line in log" :key="line" x-text="line"></li>
</ul>
<p x-show="log.length === 0" class="">watchers seeded — change something.</p>
</div>
Result
watchers seeded — change something.