# Input A styled text input with optional label and description. Supports all standard HTML input types and HTMX attributes. --- ## HTML structure ``` div.flex.flex-col.gap-1.5 label[for={id}].text-sm.font-medium ← omitted when label is empty {label text} input[type, id, name, placeholder, class, $$HxAttrs$$] p.text-sm.text-muted-foreground ← omitted when description is empty {description text} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `flex h-10 w-full rounded-md border border-input bg-background` | Full-width 40px height field with border | | `px-3 py-2 text-sm` | Inner padding and text size | | `ring-offset-background` | Focus ring offset matches the page background | | `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2` | Keyboard-visible focus ring | | `disabled:cursor-not-allowed disabled:opacity-50` | Disabled state | | `placeholder:text-muted-foreground` | Placeholder inherits muted color | --- ## Constructor signature ```csharp public Input( string id, string name = "", string inputType = "text", string placeholder = "", string label = "", string description = "", string extraClasses = "", string hxAttrs = "") ``` | Parameter | Description | |---|---| | `id` | Element id and label `for` target | | `name` | Form field name | | `inputType` | HTML type attribute: `text` / `email` / `password` / `number` / `search` / `tel` / `url` / `date` / `time` | | `placeholder` | Placeholder text | | `label` | Visible label above the field | | `description` | Helper text below the field | | `extraClasses` | Additional Tailwind classes on the input | | `hxAttrs` | Verbatim HTMX / data attributes appended to the input | --- ## Usage examples ### Email and password fields ```csharp new Input( id: "email", name: "email", inputType: "email", placeholder: "you@example.com", label: "Email address") new Input( id: "password", name: "password", inputType: "password", placeholder: "••••••••", label: "Password", description: "At least 8 characters") ``` ### Search with HTMX live search ```csharp new Input( id: "search", name: "q", inputType: "search", placeholder: "Search...", hxAttrs: """hx-get="/search" hx-target="#results" hx-trigger="keyup changed delay:300ms"""") ``` ### Number input with constraints (via extraClasses / hxAttrs) ```csharp new Input( id: "quantity", name: "qty", inputType: "number", label: "Quantity", hxAttrs: """min="1" max="100" step="1"""") ``` ### URL input ```csharp new Input( id: "website", name: "websiteUrl", inputType: "url", placeholder: "https://example.com", label: "Website", description: "Include https://") ``` ### Reading in a form handler ```csharp public record Command( [property: FromForm] string Email, [property: FromForm] string Password ); ``` --- ## Tips and tricks - `inputType: "password"` does not add any server-side security — validate and hash passwords in your handler (see [AuthService](../../Htmx.ApiDemo/Data/AuthService.cs)). - `hxAttrs` is verbatim — you can add HTML attributes like `min`, `max`, `step`, `autocomplete`, `required`, `readonly`, or `aria-*` here. - For a pre-filled input (edit form), there is no `value` parameter in the constructor — add `value="..."` via `hxAttrs`: `hxAttrs: $"""value="{Html.Encode(existingValue)}" """`. - For date and time inputs (`inputType: "date"` / `"time"`), the browser renders a native picker — consider `Calendar` or `TimePicker` for a custom-styled experience. - Pair with `Alert` (destructive variant) or a description to show server-side validation errors beneath the field. - Pair with `Alert` (destructive variant) or a description to show server-side validation errors beneath the field. --- ## Complete page example **`Templates/ContactPage.htmx`** ```html

Contact us

$$ErrorAlert$$
$$AntiforgeryToken$$
$$NameField$$ $$EmailField$$ $$SubjectField$$
$$SubmitBtn$$
``` **`Templates/ContactPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ContactPage : ContactPageBase { private readonly IHtmxComponent _error; private readonly IHtmxComponent _name; private readonly IHtmxComponent _email; private readonly IHtmxComponent _subject; private readonly IHtmxComponent _submit; private readonly byte[] _afToken; public ContactPage(IAntiforgery af, HttpContext ctx, string? errorMessage = null) { var tokens = af.GetAndStoreTokens(ctx); _afToken = $"""""".ToUtf8Bytes(); _error = errorMessage is not null ? new Components.Alert(title: "Please fix the errors below", description: errorMessage, variant: "destructive") : HtmxEmpty.Instance; _name = new Components.Input(id: "name", name: "name", label: "Full name", placeholder: "Jane Doe"); _email = new Components.Input(id: "email", name: "email", label: "Email", placeholder: "you@example.com", inputType: "email"); _subject = new Components.Input(id: "subject", name: "subject", label: "Subject", placeholder: "How can we help?"); _submit = new Components.Button("Send message", type: "submit"); } protected override void RenderErrorAlert(HtmxRenderContext ctx) => _error.Render(ctx.Next()); protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken); protected override void RenderNameField(HtmxRenderContext ctx) => _name.Render(ctx.Next()); protected override void RenderEmailField(HtmxRenderContext ctx) => _email.Render(ctx.Next()); protected override void RenderSubjectField(HtmxRenderContext ctx) => _subject.Render(ctx.Next()); protected override void RenderSubmitBtn(HtmxRenderContext ctx) => _submit.Render(ctx.Next()); } ``` **POST handler** ```csharp [Handler] [MapPost("/contact")] public static partial class PostContactHandler { public record Command( [property: FromForm] string Name, [property: FromForm] string Email, [property: FromForm] string Subject); private static Task HandleAsync( [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct) { if (string.IsNullOrWhiteSpace(cmd.Name) || string.IsNullOrWhiteSpace(cmd.Email)) { return ctx.WriteHtmxPage( new ContactPage(af, ctx, "Name and email are required."), title: "Contact"); } // Send email or persist enquiry… return Task.FromResult(Results.Redirect("/contact/thank-you")); } } ``` **`AppJsonSerializerContext.cs`** ```csharp [JsonSerializable(typeof(PostContactHandler.Command), TypeInfoPropertyName = "ContactCommand")] ```