Fragments

Fragments are the lightweight tier of Fast's Live toolkit. The idea is simple: a form or an input makes a request, your handler returns a small piece of HTML, and Fast swaps that HTML into the page. No component classes, no state to carry, just routes that return fragments. If components felt like a lot, start here.

Enabling fragments

Turn on the fragments runtime with use_live(), and put live_scripts() in your page's <head> so the browser loads the tiny client:

use_views(__DIR__ . '/views');
use_live();

get('/', fn (): string => render('page'));

The client runtime is served automatically, versioned and cached, from a /_fast/... URL that use_live() registers for you.

Driving requests with f-* attributes

You wire up behavior with f-* attributes right in your HTML. A live search box, for example, fires a GET request as the user types and swaps the results into a target element:

<form action="/search" method="get">
	<input name="q" placeholder="type to search..."
		f-get="/search" f-trigger="input debounce 200ms"
		f-target="#results" f-indicator="loading" autocomplete="off">
</form>
<ul id="results"></ul>

The handler returns just the <li> results, not a whole page:

get('/search', function (): string {
	$q = strtolower((string) request()->query('q', ''));
	$matches = search($q);

	return render('results', ['matches' => $matches]);
});

The f-* vocabulary

The attributes read declaratively. The common ones you will use:

AttributeWhat it does
f-get / f-postFire a request to a URL on the trigger event.
f-triggerWhich event fires it, with modifiers like input debounce 200ms.
f-targetA CSS selector for where the returned HTML goes.
f-swapHow to place it: innerHTML, beforeend, and so on.
f-indicatorA class toggled on an element while the request is in flight.
f-oobMarks a returned fragment as an out-of-band update to another target.

Appending and out-of-band updates

A todo form is a nice example of two ideas at once: appending a new item and updating a counter elsewhere on the page. The form posts, appends the new <li>, and the response also carries an out-of-band update for the count:

<form f-post="/todos" f-target="#list" f-swap="beforeend" action="/todos" method="post">
	<?= csrf_field() ?>
	<input name="text" placeholder="new todo" required>
	<button>Add</button>
</form>
<ul id="list"></ul>
post('/todos', fn (): string => render('added', [
	'todo' => validate(request()->form(), ['text' => 'required|string'])['text'],
	'count' => new_count(),
]));

Progressive enhancement for free

Notice that the search and todo examples both use real <form> elements with action and method attributes. That is on purpose. If JavaScript is disabled or fails to load, the forms still submit natively and your app keeps working, just with full-page reloads instead of fragment swaps. The csrf_field() in the form covers that native fallback path.

Always escape interpolated values in your fragment views with e(). Fragments, and especially out-of-band updates, can rewrite arbitrary page targets, so unescaped output is particularly dangerous here. Return small fragments, not full pages.

When you need the server to push updates to the browser on its own, rather than in response to a user action, look at Server-Sent Events next.