Example — File upload
Avatar upload with loading/progress affordances.
class AvatarForm : Component(), WithFileUploads { var photo: TemporaryUploadedFile? = null var path: String? = null
fun save() { validate() path = photo?.store("avatars") photo = null }
override fun rules() = mapOf( "photo" to listOf("required", "file", "max:2048"), )
override fun template() = """ <form wire:submit="save"> <input type="file" accept="image/*" wire:model="photo" />
<div x-data="{ progress: 0 }" x-on:brew-upload-progress="progress = ${'$'}event.detail.progress" > <progress max="100" x-bind:value="progress"></progress> </div>
<span wire:loading wire:target="photo">Uploading…</span> @if(errors['photo']) <em>{{ errors['photo'][0] }}</em> @endif
@if(path) <p>Stored at {{ path }}</p> @endif
<button type="submit" wire:loading.attr="disabled">Save</button> </form> """.trimIndent()}public class AvatarForm extends Component implements WithFileUploads { public TemporaryUploadedFile photo; public String path;
public void save() { validate(); if (photo != null) { path = photo.store("avatars"); photo = null; } }
@Override public Map<String, List<String>> rules() { return Map.of("photo", List.of("required", "file", "max:2048")); }
@Override public String template() { return """ <form wire:submit="save"> <input type="file" accept="image/*" wire:model="photo" /> <button type="submit">Save</button> </form> """; }}JS helpers when you drive uploads from Alpine: $wire.$upload, $uploadMultiple, $cancelUpload, $removeUpload. Full config: File uploads.