Brew forms
Forms in Brew keep values on the server component. wire:model syncs inputs into public properties; wire:submit runs an action without a full page navigation. Validation, form objects, and UI directives (wire:loading, wire:dirty, …) round out the slice.
Model binding
Section titled “Model binding”<input type="text" wire:model="email" /><textarea wire:model="bio"></textarea><input type="checkbox" wire:model="agree" /><select wire:model="country">…</select>Supported controls: text, textarea, checkbox (boolean or array), radio, select (single/multiple), number, date. Custom Alpine controls work when they declare x-modelable — wire:model binds through Alpine’s x-model.
Modifiers
Section titled “Modifiers”| Modifier | Behaviour |
|---|---|
| (default) | Sync on change / blur-equivalent deferral used by the runtime |
.live |
Sync on every input event (coalesced on the request bus) |
.blur |
Sync when the field blurs |
.change |
Sync on change |
.lazy |
Alias-style deferred sync |
.debounce / .debounce.500ms |
Debounce updates |
.throttle / .throttle.500ms |
Throttle updates |
.number |
Cast to number |
.boolean |
Cast to boolean |
.fill |
Initial fill behaviour for certain controls |
.self |
Only events from the element itself |
.deep |
Deep watch for nested objects |
<input wire:model.live="query" /><input wire:model.blur="email" /><input wire:model.number="age" /><input wire:model.debounce.500ms="search" />Submit
Section titled “Submit”<form wire:submit="save"> …</form>wire:submit implies .prevent. While the request is in flight, Brew disables submit buttons / selects and marks text inputs read-only, then restores them when the response finishes.
Confirm before running an action:
<button wire:click="delete" wire:confirm="Delete this post?">Delete</button><button wire:click="wipe" wire:confirm.prompt="Type DELETE|DELETE">Wipe</button>Validation
Section titled “Validation”Annotate properties with @Validate (or the @Rule alias), override rules() / messages() / validationAttributes(), and call validate() / validateOnly() from actions. Failures fill an ErrorBag that is persisted in memo.errors, exposed to Crema as errors, and available in JS as $wire.$errors.
class Profile : Component() { @Validate("required", "email") var email = ""
override fun rules() = mapOf( "email" to listOf("required", "email", Rules.unique("users", "email").ignore(id).toString()), )
fun save() { validate() // persist… }
override fun template() = """ <form wire:submit="save"> <input type="email" wire:model="email" /> @if(errors['email']) <em>{{ errors['email'][0] }}</em> @endif <button type="submit">Save</button> </form> """.trimIndent()}public class Profile extends Component { @Validate({"required", "email"}) public String email = "";
@Override public Map<String, List<String>> rules() { return Map.of("email", List.of("required", "email")); }
public void save() { validate(); }
@Override public String template() { return """ <form wire:submit="save"> <input type="email" wire:model="email" /> <button type="submit">Save</button> </form> """; }}Real-time: properties with @Validate(onUpdate = true) (the default) re-validate when wire:model updates them. Use onUpdate = false to wait for an explicit validate() call.
Form objects
Section titled “Form objects”Nest a Form subclass as a public property. Call form.validate(), fill(), all(), only(), except(), reset(), pull() — keys are local to the form (title), not form.title.
Error bag helpers
Section titled “Error bag helpers”| API | Role |
|---|---|
addError / resetValidation / resetErrorBag |
Mutate the bag |
getErrorBag / setErrorBag |
Replace / read |
$wire.$errors.has / first / get / all / clear |
Browser-side MessageBag shape |
Brew.test(…).assertHasErrors("email").assertHasNoErrors("name") covers the testing surface.
Loading, dirty, and offline UI
Section titled “Loading, dirty, and offline UI”<button wire:click="save">Save</button><span wire:loading wire:target="save">Saving…</span><span wire:dirty wire:target="title">Unsaved</span><span wire:offline>You are offline</span>wire:loading(+.class/.attr/.remove/.delay.*/ display modifiers) toggles while a matching request is in flight.wire:target/wire:target.exceptscope it; requesting elements also getdata-loading="true".wire:dirtyreflects ephemeral vs server-confirmed state;$wire.$dirty()/$wire.$dirty('title')for expressions.wire:offlinefollows the browser online/offline events.wire:poll.5s/.keep-alive/.visiblerefreshes on an interval.
Show, text, bind, cloak, ref, transition
Section titled “Show, text, bind, cloak, ref, transition”<div wire:cloak>…</div><div wire:show="open">…</div><span wire:text="label"></span><a wire:bind:href="url">Link</a><input wire:ref="email" /><div wire:transition="panel">…</div>$wire.$refs.email (or $wire.$refs('email')) returns the element. @Transition(type = "forward") on an action (or $this.transition("forward") / skipTransition()) emits effects.transition for the View Transitions API during morph.
- Actions — what submit calls
- Properties — state helpers
- JavaScript —
$wire, hooks, morph - Testing —
assertHasErrors