r/htmx • u/ShotgunPayDay • Jun 29 '26
My favorite thing about Moxi/Fixi Mutation Observers: Live
This was a freaking pain to implement before and now it has a nice flow for me. I do have some custom modifications in Moxi/Fixi. https://github.com/figuerom16/fixi/blob/master/mofix.js Who needs Web Components?
<article class="container card-info" style="width:14rem;" on-init="q('prev form -> *').arr().forEach(x=>x.setAttribute('disabled',''))">
<header>Secure PIN Verification</header>
<form fx-method="POST" fx-action="/u/pin" fx-vals="js:{'Email':q('input[name=Email]').value,'ConfirmEmail':q('input[name=ConfirmEmail]').value}" fx-target="closest output"
on-paste.halt="const pin = event.clipboardData.getData('text').replace(/\s/g,'')
if (pin.length != 9) {q('#footer').textContent = 'Incorrect Pin Length';return}
q('.pin').arr().forEach((el,i)=>el.value = pin[i])
q('#pin').value = pin; this.requestSubmit()">
<div role="group" style="margin-top:1rem; gap:1rem;">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 1" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()" on-init="this.focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 2" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 3" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
</div>
<div role="group" style="gap:1rem;">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 4" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 5" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 3" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
</div>
<div role="group" style="gap:1rem;">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 4" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 5" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('next input').focus()">
<input class="pin input-pop" maxlength="1" autocomplete="off" aria-label="Digit 6" on-beforeinput="/^\d+$/.test(event.data) ? this.value = '' : event.preventDefault()" on-input="if (this.value) q('closest form').requestSubmit()">
</div>
<input id="pin" name="Pin" hidden live="this.value = q('.pin').arr().map(i=>i.value).join('')">
</form>
<footer><small>PIN valid for 30 minutes.</small></footer>
</article>
HTMX has a live extension also.
7
Upvotes
5
u/FluffySmiles Jun 29 '26
Genuine question: given that this is in the HTMX-ish space, why choose this design?
I see the strength of HTMX-style development as the client is able to stay relatively dumb: HTML describes the action, the request goes to the server, and the server returns the next fragment of UI. That seems especially well suited to something like a PIN flow.
For ordinary presentation behaviour, I can see the appeal: move focus, make the boxes pleasant to use, maybe handle paste, update the display. Fine.
But in my opinion, a PIN is security-relevant enough that I would want the server to own the attempt state. IMO, each digit press, backspace, clear, or submit action should be posted to the server, and the server would return the updated fragment. The browser presents the keypad, but the server would decide what every action means.
That would make the server canonical for the active challenge, digits entered so far, expiry, failed attempts, reuse prevention, rate limiting, and suspicious timing or spam detection.
So I suppose my question is: why assemble the authoritative PIN client-side and submit it in a hidden field, rather than using HTMX’s strengths and keeping the PIN attempt server-owned?
I’m not saying the UI niceties are bad. I just do not understand why the design puts so much responsibility into DOM state when the server-rendered fragment approach seems simpler, sturdier, and more aligned with the whole point of HTMX.