Skip to content

x-interact:submit ​

x-interact:submit handles a form submission without reloading the page. Its expression typically calls a PHP island action.

Usage ​

For writable title state and an exposed save() action:

html
<form id="article-draft-form" x-interact:submit="$server.call('save')">
  <label for="title">Title</label>
  <input
    id="title"
    name="title"
    type="text"
    required
    x-interact:model="title"
  />
  <button type="submit">Save article</button>
</form>
php
<?= $this->Form->create(null, [
    'url' => false,
    'id' => 'article-draft-form',
    'x-interact:submit' => '$server.call("save")',
]) ?>
    <?= $this->Form->control('title', [
        'label' => 'Title',
        'value' => $title,
        'required' => true,
        'x-interact:model' => 'title',
    ]) ?>
    <?= $this->Form->button('Save article') ?>
<?= $this->Form->end() ?>

The expression runs in Alpine scope and must include the call. save() needs #[Action]. The application owns validation and persistence; see the complete article form.

Bind an entire native form with x-interact:model="data" when writable array data holds its draft. Names such as title and comments[0][body] then map to data.title and data.comments.0.body. See whole-form binding. x-interact:submit invokes and protects the submission; by itself it does not collect unbound controls.

Submission protection ​

Interact blocks repeated submissions of the same form until its expression settles, including time queued behind another request. It temporarily disables owned submit buttons and sets the form's aria-busy to true. Inputs remain editable; newer edits survive the response.

Success, validation feedback, and request failures release the protection. Application-owned disabled states are preserved. Other forms and nested islands remain independent. Stable unique form IDs allow locks to follow replacements.

Synchronization without saving ​

Use x-interact:submit="$server.sync()" for a preview that only sends current state and rerenders. No application action or automatic save is introduced.

Action arguments ​

Pass JSON-compatible arguments in the expression:

html
<form x-interact:submit="$server.call('save', 'publish')">
  <!-- Bound editor controls. -->
  <button type="submit">Publish</button>
</form>
php
<?= $this->Form->create(null, [
    'url' => false,
    'x-interact:submit' => '$server.call("save", "publish")',
]) ?>
    <!-- Bound editor controls. -->
    <?= $this->Form->button('Publish') ?>
<?= $this->Form->end() ?>

This assumes a declared action such as save(string $mode). Validate allowed modes and authorize any record arguments in PHP.

Native validation and external buttons ​

Browser constraints such as required still apply. Enter and requestSubmit() use the protected submit path. Direct form.submit() bypasses submit events. An external submit button participates when it belongs to the same island:

html
<button type="submit" form="article-draft-form">Save article</button>

Feedback ​

Use $server.loading() for delayed visual feedback and $server.error for request failures. Application validation messages use FormHelper or $server.errors.

Custom submission functions ​

Return the request promise from an Alpine method so protection lasts until it settles:

js
submitArticle() {
  return this.$server.call('save');
}

Register this method in an Alpine object, attach it inside the island, and use x-interact:submit="submitArticle()".

Options ​

The directive requires a native form and a non-empty expression. It accepts no modifiers. Do not add a second @submit handler to the same form.

Ordinary $server.call() buttons remain repeatable unless the application chooses to disable them. Browser protection does not provide server-side exactly-once execution or automatic retries.