Effects & results
handle() returns an ActionResult: a success/failure flag, optional data, and a list of effects
the client runs in order once the action responds.
Results
Section titled “Results”Build a result from one of two constructors, optionally passing data:
ActionResult::success(['id' => $product->id]);ActionResult::failure(['reason' => 'locked']);Then chain effects — each returns a new result, so they read as a pipeline:
return ActionResult::success() ->toast('Archived.') ->reloadComponent('app.products');Effects
Section titled “Effects”| Effect | What it does |
|---|---|
->toast($message, $variant) | Shows a toast. Variant defaults to success. |
->callout($callout) | Shows a persistent in-flow banner in the layout’s callout slot. |
->reloadComponent($id) | Re-fetches a single component (e.g. the table the action changed). |
->reloadPage() | Reloads the current page’s props. |
->to($url) / ->toRoute($name, $params) / ->back() | Navigates to a URL, a named route, or the previous page. |
->download($url) | Triggers a file download. |
->openModal($id) / ->closeModal($id) | Opens or closes a modal by id (closeModal() closes the current modal). |
->resetForm($id) | Resets a form to its initial values (resetForm() resets the current form). |
->localeChange($locale) | Persists the frontend locale and dispatches lattice:locale-change. |
->toggleSidebar($target) | Toggles the layout sidebar (optionally a named target). |
return ActionResult::success() ->toast('Report ready.', Variant::Success) ->download(route('reports.download', $report));Toasts
Section titled “Toasts”->toast() accepts a message and an optional Variant (Success, Error, Warning, Info).
The variant can come first or second — both ->toast('Saved.') and
->toast(Variant::Error, 'Could not save.') read naturally. Pass a ToastMessage instead to
set a lifetime, control dismissal, or attach a link or action — see Toasts.
Callouts
Section titled “Callouts”A callout is a persistent in-flow banner — appropriate for warnings, trials, subscription notices, or
any message that should stay visible until the user acts. Unlike a toast, a callout is not transient:
it appears in the layout where the Callouts slot is placed and stays until the user dismisses it.
There is no duration or auto-dismiss, and navigating between pages within the same layout does not
clear it.
Build a Callout value object and pass it to ->callout():
use Lattice\Lattice\Core\Enums\Variant;use Lattice\Lattice\Core\Values\Callout;
return ActionResult::success() ->callout( Callout::make(Variant::Warning, 'Your trial ends in 3 days.') ->title('Trial ending') ->link('Upgrade', '/billing') );The Callout builder options:
| Method | Effect |
|---|---|
->title($string) | Optional heading above the message. |
->dismissible(bool) | Show or hide the close button (default: dismissible). |
->link($label, $href, $method?) | Render a link in the callout ($method defaults to HttpMethod::Get). |
->action($component) | Render an action instead of a link. |
Callouts are always persistent — there is no duration or auto-dismiss.
Placing the slot in your layout
A callout only renders where the Callouts layout slot is placed. Add Callouts::make() to the
layout’s schema(), typically between the header bar and Outlet::make():
use Lattice\Lattice\Layouts\Components\Callouts;use Lattice\Lattice\Layouts\Components\Outlet;
public function schema(): array{ return [ $this->headerBar(), Callouts::make(), Outlet::make(), ];}Callout vs. toast
| Callout | Toast | |
|---|---|---|
| Placement | In-flow, where the layout slot is | Overlay, anchored bottom center |
| Persistence | Always persistent | Auto-dismisses after a duration |
| Scope | Requires the layout slot | Global — rendered by the <Toaster> |
Use a callout when the message warrants visible, persistent attention; use a toast for transient confirmations. See Toasts for toast details.
Refreshing what changed
Section titled “Refreshing what changed”After an action mutates data, refresh just the affected component rather than the whole page.
->reloadComponent() takes the component id — a table’s #[AsTable] id, for example — so only that
component re-fetches:
return ActionResult::success()->reloadComponent('app.products');Flashing effects without an action
Section titled “Flashing effects without an action”Effects::flash() sends any effect(s) into the session and delivers them with the next Inertia
response — no ActionResult needed. Use it from a controller, before returning a redirect, in an
event listener, or in middleware — anywhere outside an action:
use Lattice\Lattice\Core\Values\Callout;use Lattice\Lattice\Core\Enums\Variant;use Lattice\Lattice\Facades\Effects;use Lattice\Lattice\Effects\Effect;
// Flash a callout after a controller redirectEffects::flash( Effect::callout( Callout::make(Variant::Info, 'Your export is being processed.') ->title('Export queued') ));
return redirect('/exports');Pass multiple effects to flash them all at once:
Effects::flash( Effect::toast(Variant::Success, 'Settings saved.'), Effect::callout(Callout::make(Variant::Warning, 'Some changes require a page reload.')),);The flashed effects are stored in the latticeEffects session bag, drained on the next request, and
run through the normal client-side effect pipeline in order.
How effects reach the client
Section titled “How effects reach the client”The result serializes to { ok, data, effects }. Each effect carries a type discriminant
(toast, callout, reloadComponent, reloadPage, redirect, download, openModal,
closeModal, resetForm, localeChange, toggleSidebar) and its payload; the client dispatches them
in order.
Each type is declared once on its PHP class via #[AsEffect('…')] and generated into the
discriminated Effect union, so the PHP helpers and the client dispatcher stay in lockstep.
Custom effects
Section titled “Custom effects”The effect system is extensible — define your own effect type and a client handler for it, the same way components and columns extend the renderer.
On the server, an effect is a readonly value object marked with #[AsEffect], where the constructor
arguments are the wire payload:
use Lattice\Lattice\Effects\Attributes\AsEffect;use Lattice\Lattice\Effects\Effect;
#[AsEffect('confetti')]final readonly class ConfettiEffect extends Effect{ public function __construct(public int $pieces = 100) {}}Return it from handle() with ->effect(), alongside any built-ins:
return ActionResult::success() ->toast('Onboarding complete!') ->effect(new ConfettiEffect(pieces: 250));On the client, author a handler with effectHandler("confetti", …) and register it through your
registry plugin’s effects map. The handler receives
the typed payload:
import { effectHandler } from "@lattice-php/lattice";
const confetti = effectHandler("confetti", (effect) => { launchConfetti(effect.pieces);});Unknown effect types are skipped with a console warning, so a server effect with no registered handler fails soft rather than breaking the page.