Avatar pickers
The avatar picker pattern used on user profile and account settings pages. Supports uploading a new image and removing an existing one, with an initials fallback when no avatar is set.
Without avatar (initials fallback)
When no avatar is uploaded, the Large avatar displays initials. Only the upload button is shown.
JPEG, PNG, WebP, or GIF. Max 2MB.
<div id="avatar-picker">
<div class="flex flex-col sm:flex-row items-center gap-4">
<div class="shrink-0">
<koala-avatar size="Large"
user-id="@Model.Id"
first-name="@Model.FirstName"
last-name="@Model.LastName"
has-avatar="false" />
</div>
<div class="flex flex-col sm:flex-row gap-3
w-full sm:w-auto">
<form method="post"
asp-page-handler="UploadAvatar"
enctype="multipart/form-data"
x-target="avatar-picker"
x-data="{ fileName: '' }">
<label class="cursor-pointer inline-flex
items-center justify-center
text-white bg-brand border
border-transparent
hover:bg-brand-strong
font-medium rounded-lg px-4 py-2
w-full sm:w-auto">
<span x-text="fileName || 'Upload image'">
</span>
<input type="file" name="avatar"
accept="image/jpeg,image/png,
image/webp,image/gif"
class="hidden"
x-on:change="fileName =
$el.files[0]?.name || '';
if (fileName) {
$el.closest('form')
.requestSubmit();
}" />
</label>
</form>
</div>
</div>
<p class="mt-3 text-sm text-gray-500
dark:text-gray-400">
JPEG, PNG, WebP, or GIF. Max 2MB.
</p>
</div>
With avatar (image + remove)
When an avatar exists, both the upload and remove buttons are shown. The remove button uses the Danger Outlined variant.
JPEG, PNG, WebP, or GIF. Max 2MB.
<div id="avatar-picker">
<div class="flex flex-col sm:flex-row items-center gap-4">
<div class="shrink-0">
<koala-avatar size="Large"
user-id="@Model.Id"
first-name="@Model.FirstName"
last-name="@Model.LastName"
has-avatar="true" />
</div>
<div class="flex flex-col sm:flex-row gap-3
w-full sm:w-auto">
<!-- Upload form -->
<form method="post"
asp-page-handler="UploadAvatar"
enctype="multipart/form-data"
x-target="avatar-picker"
x-data="{ fileName: '' }">
<label class="cursor-pointer inline-flex
items-center justify-center
text-white bg-brand
font-medium rounded-lg px-4 py-2
w-full sm:w-auto">
<span x-text="fileName || 'Upload image'">
</span>
<input type="file" name="avatar"
accept="image/jpeg,image/png,
image/webp,image/gif"
class="hidden"
x-on:change="fileName =
$el.files[0]?.name || '';
if (fileName) {
$el.closest('form')
.requestSubmit();
}" />
</label>
</form>
<!-- Remove form -->
<form method="post"
asp-page-handler="RemoveAvatar"
x-target="avatar-picker">
<button type="submit"
koala-btn="Danger"
koala-btn-variant="Outlined"
class="w-full sm:w-auto">
Remove
</button>
</form>
</div>
</div>
<p class="mt-3 text-sm text-gray-500
dark:text-gray-400">
JPEG, PNG, WebP, or GIF. Max 2MB.
</p>
</div>
Tray vs full page mode
The avatar picker partial detects whether it is rendered inside a side tray or as a full page. In tray
mode, the form uses x-target="avatar-picker"
for AJAX updates. In full page mode, it uses formnoajax
for a standard form submission.
@{
var isTray = alpineTarget.Contains("side-tray-content")
|| alpineTarget.Contains("avatar-picker");
}
@if (isTray)
{
<!-- AJAX: x-target="avatar-picker" on form -->
<form method="post"
asp-page-handler="UploadAvatar"
enctype="multipart/form-data"
x-target="avatar-picker">
...
</form>
}
else
{
<!-- Full page: formnoajax -->
<form method="post"
asp-page-handler="UploadAvatar"
enctype="multipart/form-data"
formnoajax>
...
</form>
}
Key rules
- Always use the Large avatar size (
w-20 h-20) in the picker - The upload button is a styled
<label>wrapping a hidden file input - The file input auto-submits when a file is selected via
x-on:change - The label text updates to show the selected filename via
x-text - Remove button only appears when
has-avataris true - Accept
image/jpeg,image/png,image/webp,image/gifonly - The form must use
enctype="multipart/form-data" - The entire picker is wrapped in
id="avatar-picker"for AJAX targeting - Layout:
flex-col sm:flex-rowfor avatar + buttons, stacks on mobile