MarkdownCommitsConfig

name: fixi description: | fixi.js (3.4kb raw / 1.2kb brotli) is a miniature version of htmx offering simplified AJAX/HTML swap functionality. It enables any element to issue HTTP requests in response to events and place response HTML anywhere in the document using declarative attributes.

when_to_load: | LOAD when the user:

  • Works with fixi.js attributes (fx-action, fx-target, fx-swap, etc.)
  • Implements AJAX-driven HTML swaps in vanilla JS projects
  • Needs lightweight htmx-like functionality without the full library
  • Configures request handling with fixi events (fx:config, fx:swapped, etc.)
  • Implements patterns like inline editing, lazy loading, load more, form submission

scope: | The fixi skill covers:

  • All fixi.js core attributes and their usage
  • fixi event lifecycle and customization
  • Request configuration (headers, body, method, etc.)
  • Swap modes (outerHTML, innerHTML, morph, beforeend, etc.)
  • Per-element and global configuration patterns
  • Common use cases and examples
  • Integration with moxi for event handling
  • View Transitions API integration

fixi.js Skill Reference

Overview

fixi.js (3.4kb raw / 1.2kb brotli) is a miniature version of htmx and offers similar, simplified functionality. It makes it possible for any element to issue HTTP requests in response to any event, and place the response HTML anywhere in the document.

Like htmx, fixi uses attributes to add behaviors to HTML elements.

A Simple Demo

Here is a fixi-powered button that loads a fragment into a panel:

<button fx-action="/profile" 
        fx-target="#panel" 
        fx-swap="innerHTML">
    Load profile
</button>
<output id="panel"></output>

Clicking this button issues a GET /profile request & whatever HTML comes back is placed inside #panel.

This demonstrates the core idea of fixi: an element issues a request, gets some HTML back & places it somewhere in the DOM.

Attributes

The five core fixi attributes are:

attributedescriptiondefault
fx-actionthe URL to issue a request torequired
fx-methodthe HTTP verb to useGET
fx-triggerthe DOM event that fires the requesta sensible value based on element type
fx-targeta CSS selector for the element to swap intothe element itself
fx-swaphow to insert the response (outerHTML, innerHTML, beforeend, etc.)outerHTML

Events

fixi dispatches a set of events during its lifecycle:

eventwhen
fx:initjust before fixi wires up an element. Cancelable: preventDefault() skips this element.
fx:initedafter the element is fully wired up. Does not bubble.
fx:processlistened for on document; processes evt.target and its descendants. Dispatch this after manual DOM changes to pick up new fixi-powered nodes.
fx:configthe request has been triggered but not yet sent. Mutate evt.detail.cfg to inject headers, set a confirm callback, rewrite the URL, etc.
fx:beforeimmediately before fetch() is called.
fx:afterthe response has arrived, but before the swap.
fx:swappedthe response has been swapped in (and any View Transition has finished).
fx:errorthe fetch() threw an exception
fx:finallyafter every request, success or failure.

The most commonly used events are fx:config, to customize the request before it goes out, and fx:swapped, to react to freshly inserted content.

Modifying The Request Config

Each fixi request is configured through an object exposed on the fx:config event's evt.detail.cfg.

Listening for that event and mutating cfg is how you customize a request before it is sent.

fieldcontrols
cfg.actionthe URL fixi will fetch
cfg.methodthe HTTP verb
cfg.headersa plain object of headers to send
cfg.bodythe request body (FormData, string, etc.)
cfg.targetthe element the response will be swapped into
cfg.swapthe swap mode (outerHTML, innerHTML, morph, etc.)
cfg.confirman optional () => boolean callback fixi awaits before sending
cfg.transitionthe View Transition function, or false to disable
cfg.fetchthe fetch implementation (replace for mocking)

Per-Element Configuration

Attach an on-fx:config handler with moxi (or a plain addEventListener) to customize a single element's requests.

moxi exposes every key on event.detail as a bare name, so cfg resolves directly:

<button fx-action="/things/42" fx-method="DELETE"
        on-fx:config="cfg.confirm = () => confirm('Delete this thing?')">
    delete
</button>

Global Configuration

Listen on document to apply the same modification to every fixi request. This is the right place for auth headers, request-ID injection, or a uniform URL prefix:

document.addEventListener('fx:config', (e) => {
    e.detail.cfg.headers.Authorization = `Bearer ${getToken()}`
})

Default Configuration via window.fixiCfg

For values you want to set once at page load, fixi reads window.fixiCfg for default swap, transition, and headers:

<script>
    window.fixiCfg = {
        swap: "morph",
        headers: { "X-CSRF-Token": "abc123" },
    }
</script>
<script src="fixi.js"></script>

Per-element listeners always win over these defaults.

Examples

Inline Editing

A common fixi pattern is to click a row (or div) and to swap in an edit form, then submit to swap back to a display row.

Both of these are plain fixi swaps:

<tr id="row-42">
    <td>Ada</td>
    <td>
        <button fx-action="/contacts/42/edit"
                fx-target="#row-42"
                fx-swap="outerHTML">edit</button>
    </td>
</tr>

The server returns either the display row or the edit form depending on the URL. The edit form posts back to the same row id; the response replaces it with a fresh display row.

Form Submission

When the fixi-powered element is a <form>, the submit event triggers the request & fixi includes the form's inputs in the request body (or URL):

<form fx-action="/signup" fx-method="POST"
      fx-target="#status" fx-swap="innerHTML">
    <input name="email" type="email" required placeholder="email">
    <input name="password" type="password" required minlength="8" placeholder="password">
    <button type="submit">Sign up</button>
</form>
<output id="status"></output>

On submit, fixi POSTs the email and password as form data and swaps whatever HTML comes back into #status.

Click to Delete

fx-method="DELETE" issues a DELETE request: if the server responds with an empty body, the default outerHTML swap removes the targeted element from the DOM.

<li id="todo-42">
    Buy milk
    <button fx-action="/todos/42" fx-method="DELETE"
            fx-target="#todo-42" fx-swap="outerHTML">x</button>
</li>

The button targets its containing <li> so the whole row vanishes when the server responds.

Load More

The Load More button sits in the last row of a table, spanning all columns. By targeting that row and relying on the default outerHTML swap, clicking the button replaces the whole row with more data rows plus a fresh "Load more" row:

<table>
    <tbody>
        <tr><td>Ada</td><td>ada@example.com</td></tr>
        <tr><td>Grace</td><td>grace@example.com</td></tr>
        <tr id="more">
            <td colspan="2">
                <button fx-action="/people?page=2" fx-target="#more">Load more</button>
            </td>
        </tr>
    </tbody>
</table>

The server responds with the next page of <tr> rows followed by a fresh <tr id="more"> row pointing at ?page=3. When the pages run out, the server omits the placeholder row and the pagination ends naturally.

Lazy Loading

Any fixi event can be used as an fx-trigger, including the fx:init event that fires when fixi wires an element up.

That makes a "load this content asynchronously" simple to implement:

<div fx-action="/expensive-data"
     fx-trigger="fx:init">
    loading...
</div>

When fixi initializes the div it dispatches fx:init the element issues GET /expensive-data, and the response replaces the element.

Workflows

Setting Up fixi

  1. Install the library: Download fixi.js and include it in your project
  2. Add to HTML: Include the script tag, optionally with window.fixiCfg for defaults
  3. Mark up elements: Add fx-action and other attributes to interactive elements
  4. Server-side: Ensure your server returns HTML fragments matching the target selectors

Debugging fixi

  1. Check browser console for errors during initialization or request processing
  2. Listen to events: Add document.addEventListener('fx:error', e => console.log(e))
  3. Inspect network requests to verify URLs, methods, and headers
  4. Check target selectors exist in the DOM when response arrives

Common Patterns

  • Progressive Enhancement: Use fixi alongside traditional form submissions
  • Error Handling: Use fx:error to show user-friendly error messages
  • Loading States: Combine fx:before and fx:after to show/hide spinners
  • Confirmation: Use cfg.confirm for destructive actions
  • CSRF Protection: Add CSRF tokens via window.fixiCfg.headers or fx:config

Best Practices

  1. Use semantic swap modes: outerHTML for element replacement, innerHTML for content updates, beforeend for appending
  2. Keep server responses minimal: Return only the HTML fragment needed, not full page
  3. Use morph swap for smooth DOM updates without layout shifts
  4. Centralize auth via fx:config on document to avoid repetition
  5. Handle errors gracefully: Always provide user feedback for failed requests
  6. Use View Transitions for smooth page updates (set cfg.transition)
  7. Test without JavaScript: Ensure basic functionality works without fixi as fallback

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│ FIXI.JS QUICK REFERENCE                                        │
├─────────────────────────────────────────────────────────────┤
│ ATTRIBUTES                                                    │
│   fx-action    - URL (required)                               │
│   fx-method    - HTTP verb (default: GET)                    │
│   fx-trigger    - Event trigger (default: element-specific)   │
│   fx-target     - CSS selector for swap target                │
│   fx-swap       - Swap mode (default: outerHTML)              │
├─────────────────────────────────────────────────────────────┤
│ SWAP MODES                                                     │
│   outerHTML, innerHTML, beforeend, afterend, beforebegin,     │
│   afterbegin, morph, text, none                               │
├─────────────────────────────────────────────────────────────┤
│ KEY EVENTS                                                    │
│   fx:config    - Modify request before send                   │
│   fx:before    - Just before fetch()                           │
│   fx:after     - Response arrived, before swap                │
│   fx:swapped   - After swap completed                         │
│   fx:error     - Request failed                               │
│   fx:finally   - Always runs (success or failure)              │
├─────────────────────────────────────────────────────────────┤
│ CONFIG FIELDS (evt.detail.cfg)                                 │
│   action, method, headers, body, target, swap, confirm,       │
│   transition, fetch                                            │
└─────────────────────────────────────────────────────────────┘