Inputs

Use these input components to create things like search bars with authentication forms, credit card inputs, and other form inputs with combined input and label elements. Alpine.js handles the frontend interactivity, while HTMX queries the backend.

Phone number with mask

Requires Alpine.js
                                        <!--
    This example requires the mask Alpine.js plugin. 
-->
<div class="mx-auto max-w-xs">
    <label for="phone" class="block pl-0.5 text-sm font-medium text-slate-900">Phone number</label>
    <div class="mt-1.5 rounded-md shadow-sm">
        <input type="text"
               name="phone"
               id="phone"
               class="block w-full rounded-md border-0 py-1.5 px-2 text-slate-900 ring-1 ring-inset ring-slate-300 placeholder:text-slate-400 focus:ring-inset focus:ring-blue-600 sm:text-sm"
               x-mask="(999) 999-9999"
               placeholder="(555) 555-1234">
    </div>
</div>

                                    
                                        # no python is needed for this component

                                    

Email with validation

Requires Alpine.js
Requires HTMX
                                        <div x-cloak x-data="{ error: false }" class="mx-auto max-w-xs">
    <label for="email" class="block pl-0.5 text-sm font-medium text-slate-900">Email</label>
    <div class="mt-1.5 rounded-md shadow-sm">
        <input hx-post="{% url 'htmx:validate_email' %}"
               hx-target="#emailError"
               required
               type="email"
               name="email"
               id="email"
               class="block w-full rounded-md py-1.5 px-2 text-slate-900 ring-1 ring-inset placeholder:text-slate-400 focus:ring-inset focus:ring-blue-600 sm:text-sm"
               :class="error ? 'ring-red-600' : 'ring-slate-300'"
               placeholder="name@example.com">
    </div>
    <div @htmx:after-swap="error = $refs.emailError.innerHTML != ''"
         id="emailError"
         x-ref="emailError"
         class="pl-0.5 h-5 text-sm text-red-600"></div>
</div>

                                    
                                        ### urls.py
from django.urls import path

from . import views

app_name = "htmx"
urlpatterns = [
    path("htmx/validate-email/", views.validate_email, name="validate_email"),
]


### views.py
from django.core.exceptions import ValidationError
from django.core.validators import validate_email as email_validator
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST


@csrf_exempt
@require_POST
def validate_email(request):
    email = request.POST["email"]
    try:
        email_validator(email)
        return HttpResponse("")
    except ValidationError:
        return HttpResponse("Invalid email")

                                    

Password with show/hide toggle

Requires Alpine.js
                                        <div x-cloak x-data="{ showPassword: false }" class="mx-auto max-w-xs">
    <label for="password"
           class="block pl-0.5 text-sm font-medium text-slate-900">Password</label>
    <div class="relative mt-1.5 rounded-md shadow-sm">
        <input :type="showPassword ? 'text' : 'password'"
               name="password"
               id="password"
               class="block w-full rounded-md border-0 py-1.5 px-2 text-slate-900 ring-1 ring-inset ring-slate-300 placeholder:text-slate-400 focus:ring-inset focus:ring-blue-600 sm:text-sm"
               placeholder="••••••••">
        <button type="button"
                @click="showPassword = !showPassword"
                class="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-600"
                aria-label="Show password">
            <svg x-show="!showPassword"
                 xmlns="http://www.w3.org/2000/svg"
                 fill="none"
                 viewBox="0 0 24 24"
                 stroke-width="1.5"
                 stroke="currentColor"
                 aria-hidden="true"
                 class="size-5">
                <path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
                <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
            </svg>
            <svg x-show="showPassword"
                 xmlns="http://www.w3.org/2000/svg"
                 fill="none"
                 viewBox="0 0 24 24"
                 stroke-width="1.5"
                 stroke="currentColor"
                 aria-hidden="true"
                 class="size-5">
                <path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" />
            </svg>
        </button>
    </div>
</div>

                                    
                                        # no python is needed for this component

                                    

Password with requirements

Requires Alpine.js
Minimum 8 characters
At least one capital letter
At least one special character (#!$%&)
                                        <div x-data="{ password: ''  }" class="mx-auto max-w-xs">
    <label for="password"
           class="block pl-0.5 text-sm font-medium text-slate-900">Password</label>
    <div class="mt-1.5 rounded-md shadow-sm">
        <input type="password"
               name="password"
               id="password"
               x-model="password"
               class="block w-full rounded-md border-0 py-1.5 px-2 text-slate-900 ring-1 ring-inset ring-slate-300 placeholder:text-slate-400 focus:ring-inset focus:ring-blue-600 sm:text-sm"
               placeholder="••••••••">
    </div>
    <div class="mt-2">
        <div class="flex flex-row items-center gap-x-1 text-sm/6"
             :class="password.length >= 8 ? 'text-green-600' : 'text-slate-500'">
            <svg xmlns="http://www.w3.org/2000/svg"
                 fill="none"
                 viewBox="0 0 24 24"
                 stroke-width="1.5"
                 stroke="currentColor"
                 class="size-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
            </svg>
            Minimum 8 characters
        </div>
        <div class="flex flex-row items-center gap-x-1 text-sm/6"
             :class="password !== password.toLowerCase() ? 'text-green-600' : 'text-slate-500'">
            <svg xmlns="http://www.w3.org/2000/svg"
                 fill="none"
                 viewBox="0 0 24 24"
                 stroke-width="1.5"
                 stroke="currentColor"
                 class="size-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
            </svg>
            At least one capital letter
        </div>
        <div class="flex flex-row items-center gap-x-1 text-sm/6"
             :class="['!', '#', '$', '%', '&'].some(el => password.includes(el)) ? 'text-green-600' : 'text-slate-500'">
            <svg xmlns="http://www.w3.org/2000/svg"
                 fill="none"
                 viewBox="0 0 24 24"
                 stroke-width="1.5"
                 stroke="currentColor"
                 class="size-4">
                <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
            </svg>
            At least one special character (#!$%&)
        </div>
    </div>
</div>

                                    
                                        # no python is needed for this component