Skip to content
Brew

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.

<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.

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" />
<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>

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()
}

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.

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.

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.

<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.except scope it; requesting elements also get data-loading="true".
  • wire:dirty reflects ephemeral vs server-confirmed state; $wire.$dirty() / $wire.$dirty('title') for expressions.
  • wire:offline follows the browser online/offline events.
  • wire:poll.5s / .keep-alive / .visible refreshes on an interval.
<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.