on_click, on_change, on_submit) that accept an action, a list of actions, or None. When the event fires, the action runs.
For the most common case, syncing an interactive component’s value to state, the name prop handles it automatically. Input(name="query") writes to the query key on every keystroke without any explicit action. You reach for actions when you need side effects beyond syncing: showing a toast, calling your server, updating multiple keys at once, or running logic conditionally.
Two families
Prefab draws a clear line between two kinds of actions. Client actions run entirely in the browser. They execute instantly, require no network, and always succeed.SetState, ToggleState, AppendState, ShowToast, OpenLink, SetInterval: these are your tools for managing UI state and giving immediate feedback. From the user’s perspective, they’re instantaneous.
Server actions cross a network boundary. CallTool invokes an MCP tool on your server. Fetch makes an HTTP request. They’re asynchronous: they take time and they can fail. The UI needs to account for both the happy path and errors.
Most real interactions use both. A “Save” button typically sets a loading flag (client, instant), calls the server (server, async), then shows success or failure feedback (client, instant based on the outcome). The split is a design constraint that keeps the UI responsive and makes the code predictable.
| Client actions | Purpose |
|---|---|
SetState | Set a state key to a value |
ToggleState | Flip a boolean state key |
AppendState | Add an item to a state array |
PopState | Remove an item from a state array by index |
ShowToast | Display a brief notification |
OpenLink | Open a URL |
SetInterval | Schedule an action to repeat on a timer |
| Server actions | Purpose |
|---|---|
CallTool | Call an MCP tool and optionally write the result to state |
Fetch | Make an HTTP request and optionally write the result to state |
SendMessage | Send a message to the conversation (MCP hosts only) |
UpdateContext | Push structured context to the model (MCP hosts only) |
The $event variable
When an interaction fires, the component emits a value: the slider’s current position, the input’s current text, the checkbox’s checked state. That value is available inside action arguments as$event.
Most of the time you don’t need $event directly, because the name prop already syncs the component’s value to state automatically. Where $event becomes useful is in actions that need to reference the emitted value as part of a larger operation, like writing it to a different key, sending it to the server, or using it in a computation:
Chaining actions
A single interaction often needs to do more than one thing. An “Add” button might append an item to a list and then clear the input field. A “Save” button might set a loading flag and then call the server. To run multiple actions from one event, pass a list. The actions execute in order, and if any action fails, execution stops at that point so a failed server call won’t silently proceed to the cleanup steps. Here’s a crew list where the “Add” button chains two actions:AppendState pushes the input value onto the array, then SetState clears the input field. Both execute before the next render, so the user sees the item appear and the field reset simultaneously.
The list syntax [action1, action2, ...] works with any event handler: on_click, on_change, on_submit.
Callbacks: on_success and on_error
Action lists handle the setup: things to do before or alongside an interaction. Callbacks handle the outcomes: things to do after an async action completes. Every action supportson_success and on_error. They fire after the action resolves, with the outcome determining which branch runs. Both accept a single action or a list.
$error is available inside on_error callbacks; it holds the error message from the failed action.
Callbacks can themselves have callbacks, making it possible to chain dependent server calls: the result of the first determines what to fetch next.
Common patterns
Loading state
The combination of lists and callbacks is what makes loading state clean. Set a flag before the server call in the list; clear it in both callback branches so it always resolves, whether the call succeeds or fails:Populating results
For search and filter patterns, useresult_key to write the tool’s response directly into state, then have a ForEach or Slot display it:
PrefabApp state is merged into the client’s state under results. The ForEach re-renders with whatever came back.
Optimistic updates
For interactions where you’re confident the server will succeed, update state immediately and let the server confirm in the background. Revert on failure:on_error branch flips it back and shows a toast.