Overview
Colours
Typography
Icons
Dark mode
Avatar pickers
Avatars
Badges
Breadcrumbs
Buttons
Cards
Empty states
Hoowla link
Nav items
Stat cards
Steppers
Sticky headers
Switchers
Tables
Tabs
Timelines
Charts
Load more
Rich text
Tooltips
Action menus
Forms
Autocomplete
Modals
Notifications
Side trays
Confetti
Guided tour
Period filters
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>
Inline field validation
Every form field that needs per-field validation on blur must be wrapped in a div with
koala-inline-validation-for.
<div koala-inline-validation-for="Input.FieldName">
<label asp-for="Input.FieldName" class="block mb-2.5 font-medium text-gray-900 dark:text-white">Label</label>
<input asp-for="Input.FieldName" placeholder=""/>
<span asp-validation-for="Input.FieldName" class="mt-2 block"></span>
</div>
Requires a matching OnPostValidateField
handler in the page model. Custom dropdowns and radio buttons must NOT use this attribute.
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