ee8797c142
Co-authored-by: Copilot <copilot@github.com>
7.2 KiB
7.2 KiB
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
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
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
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)
new Input(
id: "quantity",
name: "qty",
inputType: "number",
label: "Quantity",
hxAttrs: """min="1" max="100" step="1"""")
URL input
new Input(
id: "website",
name: "websiteUrl",
inputType: "url",
placeholder: "https://example.com",
label: "Website",
description: "Include https://")
Reading in a form handler
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).hxAttrsis verbatim — you can add HTML attributes likemin,max,step,autocomplete,required,readonly, oraria-*here.- For a pre-filled input (edit form), there is no
valueparameter in the constructor — addvalue="..."viahxAttrs:hxAttrs: $"""value="{Html.Encode(existingValue)}" """. - For date and time inputs (
inputType: "date"/"time"), the browser renders a native picker — considerCalendarorTimePickerfor 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
<div class="max-w-md mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Contact us</h1>
$$ErrorAlert$$
<form method="post" action="/contact">
$$AntiforgeryToken$$
<div class="space-y-4 mb-6">
$$NameField$$
$$EmailField$$
$$SubjectField$$
</div>
$$SubmitBtn$$
</form>
</div>
Templates/ContactPage.htmx.cs
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 = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".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
[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<IResult> 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
[JsonSerializable(typeof(PostContactHandler.Command), TypeInfoPropertyName = "ContactCommand")]