ee8797c142
Co-authored-by: Copilot <copilot@github.com>
222 lines
7.2 KiB
Markdown
222 lines
7.2 KiB
Markdown
# 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
|
|
<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`**
|
|
```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 = $"""<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**
|
|
```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<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`**
|
|
```csharp
|
|
[JsonSerializable(typeof(PostContactHandler.Command), TypeInfoPropertyName = "ContactCommand")]
|
|
```
|