hx-live playground

Open DevTools. Type, click, change inputs. Verify state updates without server round-trips.

1. Simple form: :attr="expr" — boolean attributes

Fieldset disables on lock

:disabled="q('#lock').checked"
:hidden="!q('#lock').checked"

2. :text — computed text content

Subtotal: $

With 10% tax: $

3. :html — innerHTML binding

Note: :html is XSS-unsafe if value comes from untrusted source. Prefer :text.

4. :class — string and object forms

String form (class-set swap, leaves managed classes alone)

Use :class only when CSS pseudo-classes can't express the rule (cross-element comparison, arithmetic). For checkbox/focus/invalid state, use CSS (:has(), peer-checked:). Example below: branch on value-vs-budget comparison — CSS can't see it.

External class stays; managed classes swap. (Goes red+faded when cost > budget.)
:class="q('#cost').valueAsNumber > q('#budget').valueAsNumber
            ? 'hidden faded'
            : 'visible'"

Object form (toggle each class independently)

$
:class="{ big: price > 1000, expensive: price > 100 }"

5. :style — string and object forms

Object form

String form (CSS custom property)

:style="'--pct: ' + q('#p').valueAsNumber / 100"

6. :aria-* — always "true"/"false" (never removed)

Hidden until expanded.

7. matches() — bare alias for this.matches()

Disabled while form has any :invalid input
:disabled="matches(':has(input:invalid)')"

8. :checked and :value — property + attribute sync

Mirror: (DOM attribute AND .checked property stay in sync)

Source text:

Uppercased mirror:

9. ARIA-as-state: tabs via take()

Home content.
hx-on:click="take('aria-selected', '[role=tab]')"
:hidden="q('#tab-id').matches('[aria-selected=false]')"

10. toggle() — binary flip + N-value cycling

(class.active + aria-pressed)

(cycles through light → dark → auto)

11. <output> as computed variable

Subtotal: $

Total: $

<output :value="..."> acts as a named computed value. output.value is an alias for textContent, so the result is readable via q('#subtotal').value elsewhere. Add your own display:none (or a global rule) if you want it invisible. Use :text on <output> when you want it visible.

<output id="subtotal" style="display:none" :value="..."></output>
q('#subtotal').value                                    // read elsewhere

12. .focused via :class + :focus-within

:class="{ focused: matches(':focus-within') }"

13. Extended form: debounced live search

(empty)
hx-live="
  let term = q('#q').value;
  if (!term) { this.textContent = '(empty)'; return; }
  await debounce(300);
  this.textContent = 'searching for \"' + term + '\"…';
"

14. Extended form: cross-element writes

15. Extended form: imperative DOM ops (filter)

hx-live="
  let f = q('#filter').value.toLowerCase();
  for (let li of q('ul li'))
    li.hidden = f && !li.textContent.toLowerCase().includes(f);
"

16. attr() scope helper — unified attribute access

17. q() directional selectors

18. htmx.live.refresh() for non-DOM state

localStorage.setItem(...)
htmx.live.refresh()  // expressions reading localStorage recompute

19. Cascading data proxy (Alpine x-data style)

State lives on a container as data-* attribute. Descendants inherit reads and writes via the data scope helper (Proxy). data.foo resolves to the nearest ancestor with data-foo. Writes target the same ancestor.

Count (read at top level):

Nested child reads ancestor:

Deeper still:

<div data-count="0">
  <button hx-on:click="data.count++">+</button>
  <span :text="data.count"></span>       <!-- reactive -->
</div>

20. with (data) { ... } — multi-write handlers

JavaScript with statement scopes property lookups to data, letting you mutate multiple keys without prefixing. Use sparingly — works only in non-strict mode (hx-on / hx-live expressions, not modules).

❤️ · 🔁 · 👁

<div data-likes="0" data-shares="0">
  <button hx-on:click="with (data) { likes++; shares++ }">Boost</button>
</div>

21. Cascading data + :class for branching UI

Tab state via cascading data-tab instead of aria-selected. One state attribute, descendants read it. No take() needed since the state lives in one place.

Home panel content.
Profile panel content.
Settings panel content.
<div data-tab="home">
  <button hx-on:click="data.tab = 'home'" :class="{ active: data.tab === 'home' }">Home</button>
  <div :hidden="data.tab !== 'home'">...</div>
</div>