Skip to content

Architecture

How Flux fits around htmx without fighting it.

Expansion, not replacement

Flux's fx-* attributes are expanded into htmx's native hx-* attributes at processing time. htmx's own attribute prefix stays at its default, so raw hx-* remains an unconditional escape hatch: <a fx-get="/x" hx-target="#main"> mixes freely. Since 2.0 there is no alias layer — presets read their own option attributes only, everything else is raw hx-*.

Pipeline integration

Flux installs listeners on htmx's event pipeline — never a second network layer:

DOM event
  └─ htmx:config:request   → CSRF attach, timeout, credentials
  └─ htmx:before:request   → dedupe leader election, cache lookup
  └─ (network)
  └─ htmx:after:request    → retry decision, feedback state, action pipelines
  └─ htmx:after:settle     → persist restore, form dirty re-init
  └─ htmx:before:cleanup   → preset disconnects, generated-attribute release

Presets expand → htmx processes → Flux's registered controllers (connect) manage their elements and return disconnects on cleanup.

Ownership registry

Every hx-* attribute Flux writes is recorded per element (WeakMap) and every touched element tracked by WeakRef (deduplicated, O(1)). Consequences:

  • User-written hx-* is never overwritten — user takeovers are detected and respected.
  • Removing the source fx-* removes Flux's generated attributes on the next pass.
  • Manual DOM removal (el.remove(), fx-remove) leaves no strong references — no leaks on long-lived dashboards.

Bootstrap contract

Importing any Flux entry has no global side effects. Entry points call bootstrapFlux(), which owns the window.Flux slot, applies the duplicate policy and honours <meta name="flux-config"> autoStart. Modular and full builds can never race for the global.

See decisions/0004 for the prefix-vs-expansion rationale.