Skip to content

Views and htmx

Anvil’s own views follow these rules; use them in yours.

Token and action. Send _token and _action as hidden inputs of a form or with hx-vals on the element that sends the request (a button):

<form hx-post="<?= esc($endpoint, 'attr') ?>" hx-target="#settings" hx-swap="outerHTML"
hx-disinherit="*" hx-disabled-elt="find button[type=submit]">
<input type="hidden" name="_token" value="<?= esc($token, 'attr') ?>">
<input type="hidden" name="_action" value="save">
…
</form>

Never put the token into hx-vals of a container (div, form): hx-vals is inherited, and a boosted link inside would put the token into the URL.

Target and swap.

Pattern Use
hx-target="#id" hx-swap="outerHTML" the answer is the whole component (as in the example)
hx-target="#id" hx-swap="innerHTML" the answer is the content of a container, e.g. a dropdown menu
hx-target="#id" hx-select="#id" hx-swap="outerHTML" the answer is larger than the part to replace (Anvil’s lists)
hx-swap="none" the answer only carries events, e.g. ComponentResponse::empty()->withEvent(…)
hx-swap-oob="true" on an element of the answer updates a second part of the page in the same answer (the widgets above a list)

No inheritance. A form or container with hx-target/hx-swap passes them on to every link and button inside. Set hx-disinherit="*" (or hx-disinherit="hx-target hx-select hx-swap") on it.

Other attributes Anvil uses.

  • hx-disabled-elt="find button[type=submit]": disables the submit button while the request runs.
  • hx-sync="closest form:replace" or hx-sync="this:replace": a new request replaces a running one (search inputs).
  • hx-trigger="input changed delay:400ms, search": search while typing.
  • hx-params="_token,_action,q": sends only these fields, e.g. a search field inside a larger form.
  • hx-confirm="…": confirmation in Anvil’s dialog.
  • Links to downloads need hx-boost="false" (the body is boosted).

On a 422 answer, anvil.js shows the messages next to the fields of the form that sent the request. It finds a field by an element with data-anvil-field="<input name>" and writes the message into its [data-anvil-error] element; errors without a matching field are shown as a toast. Other errors (403, 404, 429, network) are shown as a toast.

<div class="anvil-field mb-2" data-anvil-field="comment">
<label class="form-label" for="comment">Comment</label>
<textarea class="form-control" id="comment" name="comment" rows="2" maxlength="2000"></textarea>
<div class="invalid-feedback d-block d-none" data-anvil-error></div>
</div>

novalidate on the form leaves validation to the server. Anvil’s field views render this markup already (see Forms).