Forms
Working validation demo showing every field type. In the Portal, forms use Alpine-AJAX with
koala-inline-validation-for for per-field validation on blur.
<form method="post" x-target.push="main" novalidate>
<!-- Text inputs (2-column) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div koala-inline-validation-for="Input.FirstName">
<label asp-for="Input.FirstName"
class="block mb-2.5 font-medium text-gray-900 dark:text-white">First name</label>
<input asp-for="Input.FirstName" placeholder=""/>
<span asp-validation-for="Input.FirstName" class="mt-2 block"></span>
</div>
<div koala-inline-validation-for="Input.LastName">
<label asp-for="Input.LastName"
class="block mb-2.5 font-medium text-gray-900 dark:text-white">Last name</label>
<input asp-for="Input.LastName" placeholder=""/>
<span asp-validation-for="Input.LastName" class="mt-2 block"></span>
</div>
</div>
<!-- Email with icon prefix -->
<div koala-inline-validation-for="Input.Email">
<label asp-for="Input.Email"
class="block mb-2.5 font-medium text-gray-900 dark:text-white">Email</label>
<input asp-for="Input.Email" koala-input-prefix="Email" placeholder=""/>
<span asp-validation-for="Input.Email" class="mt-2 block"></span>
</div>
<!-- Currency input with £ prefix -->
<div koala-inline-validation-for="Input.Amount">
<label asp-for="Input.Amount"
class="block mb-2.5 font-medium text-gray-900 dark:text-white">Amount</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-2.5
pointer-events-none dark:text-white">
£
</div>
<input asp-for="Input.Amount"
inputmode="numeric"
data-type="currency"
class="block w-full ps-7 pe-3 py-2.5 bg-white dark:bg-gray-700
border border-gray-200 dark:border-gray-600 text-gray-900
dark:text-white rounded-lg placeholder:gray-100"
placeholder=""/>
</div>
<span asp-validation-for="Input.Amount" class="mt-2 block"></span>
</div>
<!-- Alpine.js custom dropdown (no koala-inline-validation-for) -->
<div>
<label class="block mb-2.5 font-medium text-gray-900 dark:text-white">Category</label>
<div x-data="{ open: false, selected: '' }" class="relative"
x-on:click.outside="open = false">
<button type="button" x-on:click="open = !open"
class="flex items-center justify-between w-full px-3 py-2.5
bg-white dark:bg-gray-700 border border-gray-200
dark:border-gray-600 text-gray-900 dark:text-white rounded-lg">
<span x-text="selected || 'Select a category'"
:class="selected ? '' : 'text-gray-400'"></span>
<koala-icon name="ChevronDown" size="Small" class="text-gray-400" />
</button>
<input type="hidden" name="Input.Category" :value="selected" />
<div x-show="open" x-transition x-cloak
class="absolute z-10 mt-1 w-full bg-white dark:bg-gray-800
border border-gray-200 dark:border-gray-700 rounded-lg
shadow-lg py-1 overflow-hidden">
<button type="button" x-on:click="selected = 'Sale'; open = false"
class="block w-full px-3 py-2 text-left text-gray-900
dark:text-white hover:bg-gray-50 dark:hover:bg-gray-700"
:class="selected === 'Sale' ? 'bg-gray-50 dark:bg-gray-700' : ''">Sale</button>
<button type="button" x-on:click="selected = 'Purchase'; open = false"
class="block w-full px-3 py-2 text-left text-gray-900
dark:text-white hover:bg-gray-50 dark:hover:bg-gray-700"
:class="selected === 'Purchase' ? 'bg-gray-50 dark:bg-gray-700' : ''">Purchase</button>
<button type="button" x-on:click="selected = 'Remortgage'; open = false"
class="block w-full px-3 py-2 text-left text-gray-900
dark:text-white hover:bg-gray-50 dark:hover:bg-gray-700"
:class="selected === 'Remortgage' ? 'bg-gray-50 dark:bg-gray-700' : ''">Remortgage</button>
</div>
</div>
<span asp-validation-for="Input.Category" class="mt-2 block"></span>
</div>
<!-- Textarea -->
<div koala-inline-validation-for="Input.Notes">
<label asp-for="Input.Notes"
class="block mb-2.5 font-medium text-gray-900 dark:text-white">Notes</label>
<textarea asp-for="Input.Notes" rows="4" placeholder=""></textarea>
<span asp-validation-for="Input.Notes" class="mt-2 block"></span>
</div>
<button type="submit" koala-loading koala-btn="Primary">Submit</button>
</form>
Key rules
novalidateon every form — all validation is server-side via FluentValidationkoala-inline-validation-foron the wrapping div of every field (requires Alpine-AJAX)- Custom dropdowns and radio buttons must not use
koala-inline-validation-for koala-loadingon submit buttons for spinner and click guard- Input model properties use
initaccessors, notset - Use nullable types (
string?) to avoid ASP.NET's implicit required validation - Never save data on blur — only the submit button triggers
OnPost() - Every page model using
koala-inline-validation-formust have anOnPostValidateFieldhandler