Markdown Files
name: paxi description: | paxi.js is a miniature version of idiomorph. It swaps HTML into the DOM by morphing the old tree to match the new one instead of replacing it wholesale, preserving focus, caret position, scroll offset, and form state.
when_to_load: | LOAD when the user:
- Works with HTML morphing and DOM reconciliation
- Needs to preserve form state, focus, or scroll position during swaps
- Uses fx-swap="morph" in fixi applications
- Calls window.morph() directly for standalone morphing
- Implements lossless form swaps or UI updates
scope: | The paxi skill covers:
- Morph-based DOM swapping concepts
- Integration with fixi.js via fx-swap="morph"
- Standalone morphing with window.morph(target, html)
- id-keyed reconciliation for DOM updates
- Preserving UI state during HTML updates
paxi.js Skill Reference
Overview
paxi.js is a miniature version of idiomorph.
It swaps HTML into the DOM by morphing the old tree to match the new one instead of replacing it wholesale.
The practical benefit is that focus, caret position, scroll offset, and form state on elements are preserved when a full swap would blow them away.
Usage
paxi adds no new attributes of its own. There are two ways to invoke it:
| usage | description |
|---|---|
fx-swap="morph" | when loaded alongside fixi, paxi registers itself as the morph swap strategy on any fixi-powered element. |
window.morph(target, html) | the underlying morphing function, callable directly without fixi. |
Examples
Lossless Form Swap
A panel that reloads from /profile while preserving any in-progress input:
<button fx-action="/profile" fx-target="#profile" fx-swap="morph">
Reload Profile
</button>
<div id="profile">
<input id="nickname" placeholder="typing something here survives the swap">
<p id="bio">...</p>
</div>
Without fx-swap="morph", the input would be replaced and any in-flight text would be lost. paxi reconciles the new tree against the existing one, leaving the focused input untouched.
Default To Morphing
For an app where most swaps should be lossless, set morph as the default swap once via fixiCfg:
<script>
window.fixiCfg = { swap: "morph" }
</script>
<script src="fixi.js"></script>
<script src="paxi.js"></script>
Every fixi request now morphs unless an element overrides it with its own fx-swap.
Standalone Morphing
Outside of fixi, call window.morph() directly. The first argument is the element to morph, the second is the new HTML as a string:
morph(document.getElementById('card'), newHtml)
This is handy when you have HTML in hand from somewhere other than a fixi response (local state, a JSON API, a WebSocket message) and want id-keyed reconciliation without the rest of the request pipeline.
How It Works
paxi performs id-keyed reconciliation:
- Parse new HTML into a temporary DOM tree
- Match elements by ID - elements with the same ID in old and new trees are considered the same
- Recursively morph - for matched elements, update attributes and recursively morph children
- Preserve state - focus, caret position, scroll offset, form input values are maintained
- Handle mismatches - elements without matching IDs are added/removed as needed
Best Practices
- Use meaningful IDs - Elements that should preserve state across swaps need stable, unique IDs
- Set morph as default - For most applications,
window.fixiCfg = { swap: "morph" }provides the best UX - Use for forms - Morphing is ideal for form submissions where you want to preserve user input
- Combine with fixi - Use paxi with fixi for seamless server-driven UI updates
- Avoid for full page reloads - Morphing is designed for partial updates, not complete page replacements
- Test edge cases - Verify morphing works correctly with your specific DOM structure
- Consider performance - Morphing is generally faster than full replacement but can be slower for very large DOM trees
Common Use Cases
- Form preservation - User types in a form, submits, server returns updated form with validation errors, input state is preserved
- Modal dialogs - Modal content updates without losing focus on the currently focused input
- Tabs and panels - Switching between tabs preserves scroll position and form state
- Live search - Search results update without losing the user's scroll position
- Incremental updates - Small parts of the UI update without affecting unrelated state
- Error recovery - Server returns error response, form state remains intact for corrections
Integration with fixi
paxi is designed to work seamlessly with fixi:
- Load both libraries - Include both
fixi.jsandpaxi.jsin your page - Use
fx-swap="morph"- On any element that should use morphing instead of replacement - Or set as default - Use
window.fixiCfg = { swap: "morph" }to make morphing the default - Override per-element - Individual elements can still use other swap modes like
innerHTMLorouterHTML
Comparison to Other Approaches
| approach | preserves state | complexity | use case |
|---|---|---|---|
| paxi morph | ✅ Yes | Low | Partial UI updates |
| innerHTML | ❌ No | Low | Simple content replacement |
| outerHTML | ❌ No | Low | Element replacement |
| Manual DOM updates | ✅ Yes | High | Fine-grained control |
| Virtual DOM (React, etc.) | ✅ Yes | High | Complex applications |
Quick Reference Card
┌─────────────────────────────────────────────────────────────┐
│ PAXI.JS QUICK REFERENCE │
├─────────────────────────────────────────────────────────────┤
│ OVERVIEW │
│ Morph-based DOM swapping that preserves: │
│ - Focus state │
│ - Caret position │
│ - Scroll offset │
│ - Form input values │
├─────────────────────────────────────────────────────────────┤
│ USAGE │
│ fx-swap="morph" - With fixi elements │
│ morph(target, html) - Standalone function │
├─────────────────────────────────────────────────────────────┤
│ INTEGRATION WITH FIXI │
│ window.fixiCfg = { swap: "morph" } │
│ <script src="fixi.js"></script> │
│ <script src="paxi.js"></script> │
├─────────────────────────────────────────────────────────────┤
│ KEY FEATURES │
│ - ID-keyed reconciliation │
│ - Preserves UI state │
│ - Lightweight (~1kb) │
│ - No new attributes │
│ - Works with or without fixi │
└─────────────────────────────────────────────────────────────┘