Koala logo Design

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.

£
Reset
<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">
                &pound;
            </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

  • novalidate on every form — all validation is server-side via FluentValidation
  • koala-inline-validation-for on the wrapping div of every field (requires Alpine-AJAX)
  • Custom dropdowns and radio buttons must not use koala-inline-validation-for
  • koala-loading on submit buttons for spinner and click guard
  • Input model properties use init accessors, not set
  • 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-for must have an OnPostValidateField handler