Skip to content

Fluid user experience

Greg Bowler edited this page May 29, 2026 · 3 revisions

Writing applications for hypermedia (plain HTML over HTTP) brings a lot of benefits. URLs stay meaningful, page states stay easy to trace, and the browser and server can work together to provide unrivaled efficiency. The trade-off is that, left completely alone, these applications can sometimes feel clunky or old fashioned because every interaction is a full page load. But with a hypermedia, server-rendered application, it only takes a very small layer of client-side code to remove most of that roughness and make a hypermedia application feel fluid, while still keeping the underlying application simple and readable.

WebEngine can provide a fluid user experience without changing the basic shape of a web application. The page is still HTML, and the browser still requests URLs like normal. Forms still submit to the server, and links still point to real pages.

This is done by adding data-flux attributes within the HTML, and the Flux client-side micro framework will perform progressive enhancements to automate the client-side experience.

Full Flux documentation: https://www.php.gt/docs/Flux/

The main idea is simple: the server keeps returning complete HTML documents, and Flux swaps the matching parts of the current page with the matching parts from the new document.

Updating parts of a page dynamically

The usual starting point is an ordinary form that already works without any JavaScript:

<form method="post">
	<output data-flux="update">0</output>
	
	<button name="do" value="increment" data-flux>Increment</button>
	<button name="do" value="decrement" data-flux>Decrement</button>
</form>

With no Flux, the form submits normally and the whole page reloads. With Flux loaded in the browser:

  1. clicking the button prevents the normal submit
  2. the form is submitted in the background
  3. the server returns the normal full HTML page
  4. Flux finds the matching data-flux update target in the returned document
  5. only that part of the current page is replaced

That means we do not need to build a separate JSON API or a separate fragment-rendering system just to make one part of the page feel quicker.

The most useful update directives are:

  • data-flux="update" to replace the whole element
  • data-flux="update-inner" to keep the element but replace its contents
  • data-flux="update-link" to update an element only during Flux link navigation
  • data-flux="update-link-inner" to keep the outer element but replace only its contents during Flux link navigation
  • data-flux="update-attributes" to refresh just the element's attributes

Updating a whole element

Use update when the element itself may change:

<form method="post" data-flux="update">
	<output>0</output>
	<button name="do" value="increment" data-flux="submit">Increment</button>
</form>

This is useful when the element's attributes or outer structure may change between responses.

Updating only the contents

Use update-inner when the wrapper should stay in place:

<section class="todo-list" data-flux="update-inner">
	<ul>
		<li>Write docs</li>
	</ul>
</section>

This is common when the outer element has layout or styling responsibilities and only the inner content changes.

Updating more than one target at once

Flux is not limited to one updated element. If the returned document contains several matching Flux targets, all of them can be refreshed from the same server response.

That is useful for pages such as:

  • a pair of counters with a separate total
  • a basket page with individual line items and an order summary
  • a dashboard where one action changes several small widgets

The practical guide for this part of Flux is:

Link navigation

Flux can also enhance normal links:

<main data-flux="update-link">
	<nav>
		<a href="/reports" data-flux>Reports</a>
		<a href="/settings" data-flux>Settings</a>
	</nav>
</main>

When a Flux link is clicked, Flux fetches the destination document in the background, updates the matching target, and updates the browser history.

This keeps navigation fluid while still treating each page as a real page with its own URL and its own server-rendered HTML.

See:

Live updates

Sometimes the page needs to keep itself up to date without any user action. Flux supports that with live regions:

<time data-flux="live" data-flux-rate="5">12:00:00</time>

This tells Flux to poll the current page every five seconds and replace the matching <time> element with the version from the newly returned document.

data-flux="live-inner" works in the same way, but keeps the outer element and replaces only its contents.

Live regions are useful for:

  • clocks
  • status badges
  • queue counts
  • notifications
  • small dashboards

See:

Autosave and attribute updates

Flux also supports background submission when a form changes:

<form method="post" data-flux="update-inner">
	<input name="title" />
	<button name="do" value="save" data-flux="autosave">Save</button>
</form>

Here the button is hidden by Flux and used as the trigger for automatic saving whenever the form changes.

Attribute-only refreshes are useful when the content does not need changing, but the state does:

<button data-flux="update-attributes" disabled aria-busy="true">Save</button>

This works well for:

  • disabled
  • class
  • aria-*
  • data-*

See:

Rate limiting

Flux can rate-limit buttons, links, and live regions with data-flux-rate:

<button data-flux="submit" data-flux-rate="1">Save</button>

This allows one request per second from that control. For live regions, the same attribute defines the polling interval.

That helps prevent accidental double-clicks or repeated rapid requests from a control. It does not replace server-side validation or idempotency checks.

See:

JavaScript hooks and debugging

Flux also cooperates with your own JavaScript. When it replaces elements, it reinitialises new Flux elements and reattaches event listeners that were added through addEventListener.

That matters because without it, a DOM replacement would normally discard those listeners.

If you are debugging Flux itself or trying to understand a page's behaviour in detail, Flux also exposes a debug mode. The details are covered here:

Multi-pass rendering

Multi-pass rendering is the idea of giving the browser something useful first, then doing slower enhancement work afterwards.

For example, a page might:

  1. return a fully usable first response immediately
  2. let the browser render that page
  3. make a later request for slower-changing or heavier dynamic areas
  4. let Flux replace those areas once the second response arrives

This can improve perceived performance because the user sees something meaningful sooner, even though some work is still happening in the background.

The important part is that multi-pass rendering still fits the same WebEngine model:

  • pages are still real URLs
  • the server still returns HTML
  • the browser is still updating from server-rendered documents

It is not a separate application architecture. It is just another way to stage the work.

Flux docs

This page is only the WebEngine-side overview. For the step-by-step Flux guide, continue with the Flux docs directly:


Learn how to track speed and benchmarks in your applications.

Clone this wiki locally