Hyperscript-flavored shorthand for the htmx hx-live extension.
It rewrites shorthand expressions to the core q() API before hx-live
evaluates them. It adds syntax, not state or runtime behavior.
Note
Packaging and standalone test infrastructure are still pending.
Load scripts in this order:
htmx
└── hx-live
└── hx-live-hyperscript
The extension source is at
src/ext/hx-live-hyperscript.js.
Prefix an attribute with @ to read or write it on the current element.
<button aria-pressed="false"
hx-on:click="@aria-pressed = !@aria-pressed">
Mute
</button>The core form is:
q(this).aria.pressed = !q(this).aria.pressedThe same syntax covers each state namespace:
@data-count++
@aria-expanded = true
@.active = true
@readonly = truePrefix an attribute with ^ to use its nearest owner, starting at the current
element.
<section data-count="0">
<button hx-on:click="^data-count++">Add</button>
<output :text="^data-count"></output>
</section>section[data-count]
▲
│ ^data-count
button
The core form is:
q(this).closest.data.count++Use the same owner lookup after a selection:
q('.item').^data-open = true
q('.option').^aria-activedescendant = this.idUse #id for an ID or <.../> for any selector accepted by q().
#cart.@data-count++
<.row/>.@hidden = true
<previous input/>.@value
<.error/>.^data-invalid = true#cart → q('#cart')
<.row/> → q('.row')
<previous input/> → q('previous input')
Directional selectors use the core q() grammar:
<first .item/>
<last .item/>
<next .item/>
<previous .item/>
<closest .field/>
<.item in #panel/>Use 's to access state or a property from a selector literal.
#cart's @data-count++
<.field/>'s @valueIt is equivalent to continuing through the selected q() proxy.
Inside toggle() and take(), a sigil replaces the string attribute name.
toggle(@aria-expanded)
toggle(@data-view, 'grid', 'list')
take(@aria-selected)
take(@.active)These have the same behavior as:
toggle('aria-expanded')
toggle('data-view', 'grid', 'list')
take('aria-selected')
take('.active')| Shorthand | Core expression |
|---|---|
@data-count |
q(this).data.count |
^data-count |
q(this).closest.data.count |
@aria-expanded |
q(this).aria.expanded |
^aria-expanded |
q(this).closest.aria.expanded |
@.active |
q(this).class.active |
^.active |
q(this).closest.class.active |
@readonly |
q(this).attr.readonly |
^readonly |
q(this).closest.attr.readonly |
Use a wildcard or @class to access a whole namespace:
{ ...@data-* }
{ ...^data-* }
@class.assign({ active: true, loading: false })| Shorthand | Core expression |
|---|---|
#cart |
q('#cart') |
<.row/> |
q('.row') |
<previous input/> |
q('previous input') |
<.item in #panel/> |
q('.item in #panel') |
The extension hooks into expression compilation and rewrites supported syntax:
HTML expression
│
▼
hx-live-hyperscript
│ syntax rewrite
▼
core q() expression
│
▼
hx-live evaluation
For example:
^data-count++
↓
q(this).closest.data.count++
The rewriter skips strings, comments, regular expressions, and raw template text. Normal JavaScript keeps its meaning when a value comes first:
flags ^ mask
count < maxsrc/ext/hx-live-hyperscript.js extension source
test/tests/ext/hx-live-hyperscript.js browser tests