Quickstart
Build a counter that updates without reloading its surrounding page. You will add a PHP class, a template, and one mount call to an existing controller view.
Complete installation first: load the plugin and helper, build and serve the browser runtime, output the layout's script block, and keep CSRF middleware enabled. No database or custom JavaScript is needed for this example.
Declare an island
Create src/Island/CounterIsland.php. Interact resolves Counter to this class when the helper renders it. No registration or discovery attribute is required.
<?php
declare(strict_types=1);
namespace App\Island;
use Interact\Attribute\Action;
use Interact\Attribute\State;
use Interact\Island;
/**
* Keeps a count for one region and changes it through a server action.
*
* Save as src/Island/CounterIsland.php.
*/
class CounterIsland extends Island
{
#[State]
public int $count = 0;
/**
* Increase this island's count when its button is pressed.
*/
#[Action]
public function increment(): void
{
$this->count++;
}
}State keeps $count between requests. Without writable: true, the browser cannot assign it directly. Action exposes increment() as an intentional way to change that value.
Write the template
The inferred template receives $count on the initial render and every update:
<?php
declare(strict_types=1);
/**
* templates/island/counter.php
*
* @var int $count
*/
?>
<p aria-live="polite">Count: <?= (string)$count ?></p>
<button
type="button"
@click="$server.call('increment')"
:disabled="$server.busy"
>
Add one
</button>
<p role="alert" x-text="$server.error ? $server.error.message : ''"></p>@click invokes the PHP action through $server.call(). The Alpine shorthand :disabled="$server.busy" disables the button while a request is running. The alert displays a request failure when one occurs.
Mount it in an existing page
In a template already rendered by your application, add:
<?php
declare(strict_types=1);
/**
* templates/Articles/index.php
*
* @var \Cake\View\View $this
*/
?>
<h1>Articles</h1>
<?= $this->Interact->render('Counter') ?>Counter resolves to App\Island\CounterIsland. Its default template is templates/island/counter.php; underscored names such as article_editor resolve to ArticleEditorIsland.
Open your existing articles page. You should see Count: 0. Press Add one: the count becomes 1, and the heading and surrounding layout stay in place. Each click sends a POST to the update endpoint. If the count does not change, follow the troubleshooting checks.
Your controller needs no AJAX branch. It still renders its normal action template on the first request; later requests go directly to Interact's update endpoint.
Add an independent counter
Render the same island name again with distinct stable keys:
<?= $this->Interact->render('Counter', key: 'first') ?>
<?= $this->Interact->render('Counter', key: 'second') ?>Each starts at zero and updates independently. Reloading the page starts fresh: the count is interaction state, and this example does not save it to a database.
See Conventions for organizing related islands into matching class and template folders.
Choose your next step
| Task | Guide |
|---|---|
| Look up a directive | HTML directives |
| Look up a JavaScript bridge member | $server |
| Bind an input or pass an initial value | Properties |
| Read PHP data from Alpine or a JavaScript file | Alpine and $server |
| Call an action with arguments or show loading feedback | Actions |
| Upload and validate files | File uploads |
| Filter a larger model-backed listing | Search and pagination |
| Validate and save an editable record | Article form |
| Reuse a controller action's template | Page islands |
Application-owned Alpine
If your app already starts Alpine, disable the helper's standalone script and register Interact with that instance. Follow the application-owned Alpine setup.
Reuse an action template
An island can own the content of an action template while its normal layout stays in place. Follow whole-page islands for the cooperating island, controller, and template.