# FileInput A styled file upload field with an optional visible label and description. Supports `accept` MIME types, multiple file selection, and HTMX attributes for server-driven interactions. --- ## 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=file, id, name, accept, class, $$Multiple$$, $$HxAttrs$$] p.text-sm.text-muted-foreground ← omitted when description is empty {description text} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `file:mr-4 file:py-2 file:px-4 file:rounded-md` | Styles the browser's "Choose file" button via `::file-selector-button` | | `file:border-0 file:bg-primary file:text-primary-foreground` | Gives the file button the primary color | | `file:text-sm file:font-semibold file:cursor-pointer` | Consistent text treatment | | `hover:file:bg-primary/90` | Slight darkening on hover | | `w-full rounded-md border border-input bg-background text-sm` | Full-width field with border | --- ## Constructor signature ```csharp public FileInput( string id, string name = "", string accept = "", bool multiple = false, string label = "", string description = "", string extraClasses = "", string hxAttrs = "") ``` | Parameter | Description | |---|---| | `id` | Element id and label `for` target | | `name` | Form field name | | `accept` | MIME types or file extensions, e.g. `"image/*"` or `".pdf,.docx"` | | `multiple` | Allows selecting more than one file | | `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 ### Basic single file ```csharp new FileInput( id: "avatar", name: "avatar", accept: "image/*", label: "Profile picture", description: "PNG, JPG or GIF up to 2 MB") ``` ### Multiple files ```csharp new FileInput( id: "attachments", name: "attachments", accept: ".pdf,.docx,.xlsx", multiple: true, label: "Attachments", description: "Select one or more documents") ``` ### HTMX auto-upload on change ```csharp new FileInput( id: "import-csv", name: "csv", accept: ".csv", label: "Import CSV", hxAttrs: """hx-post="/import" hx-encoding="multipart/form-data" hx-target="#result" hx-trigger="change"""") ``` ### No label ```csharp new FileInput(id: "doc", name: "document", accept: ".pdf") ``` --- ## Tips and tricks - `accept` filters in the browser's file picker dialog but does not validate server-side — always validate the uploaded file type in your handler. - For HTMX file uploads set `hx-encoding="multipart/form-data"` in `hxAttrs`; HTMX does not infer encoding from the input type. - Multiple files are bound as a list: `IFormFileCollection` or `List` in the handler. `FromForm` attribute on the command record field is required. - To show a preview of the selected image before upload, add a small JS snippet that listens to the `change` event and sets `src` on an `` element via `URL.createObjectURL(e.target.files[0])`. - `extraClasses` is added to the `` element, not the wrapper `
` — use it for overriding width, borders, or custom ring colors. - `extraClasses` is added to the `` element, not the wrapper `
` — use it for overriding width, borders, or custom ring colors. --- ## Complete page example **`Templates/UploadPage.htmx`** ```html

Upload document

$$AntiforgeryToken$$
$$FileField$$
$$SubmitBtn$$
$$Result$$
``` **`Templates/UploadPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class UploadPage : UploadPageBase { private readonly IHtmxComponent _fileField; private readonly IHtmxComponent _submitBtn; private readonly IHtmxComponent _result; private readonly byte[] _afToken; public UploadPage(IAntiforgery af, HttpContext ctx, string? uploadedFileName = null, string? error = null) { var tokens = af.GetAndStoreTokens(ctx); _afToken = $"""""".ToUtf8Bytes(); _fileField = new Components.FileInput( id: "document", name: "document", accept: ".pdf,.docx", label: "Select a file", description: "PDF or Word document, max 10 MB"); _submitBtn = new Components.Button("Upload", type: "submit"); _result = error is not null ? new Components.Alert(title: "Upload failed", description: error, variant: "destructive") : uploadedFileName is not null ? new Components.Alert(title: "Uploaded!", description: $"Saved as: {uploadedFileName}") : HtmxEmpty.Instance; } protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken); protected override void RenderFileField(HtmxRenderContext ctx) => _fileField.Render(ctx.Next()); protected override void RenderSubmitBtn(HtmxRenderContext ctx) => _submitBtn.Render(ctx.Next()); protected override void RenderResult(HtmxRenderContext ctx) => _result.Render(ctx.Next()); } ``` **GET + POST handlers** ```csharp [Handler] [MapGet("/upload")] public static partial class GetUploadHandler { public record Query(); private static Task HandleAsync(Query _, HttpContext ctx, IAntiforgery af, CancellationToken ct) => ctx.WriteHtmxPage(new UploadPage(af, ctx), title: "Upload"); } [Handler] [MapPost("/upload")] [DisableRequestSizeLimit] public static partial class PostUploadHandler { public record Command([property: FromForm] IFormFile? Document = null); private static async Task HandleAsync( [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct) { if (cmd.Document is null) { return await ctx.WriteHtmxPage( new UploadPage(af, ctx, error: "No file was selected."), title: "Upload"); } var allowedTypes = new[] { ".pdf", ".docx" }; var ext = Path.GetExtension(cmd.Document.FileName).ToLowerInvariant(); if (!allowedTypes.Contains(ext)) { return await ctx.WriteHtmxPage( new UploadPage(af, ctx, error: "Only PDF and Word files are allowed."), title: "Upload"); } var safeName = Path.GetRandomFileName() + ext; var savePath = Path.Combine("uploads", safeName); await using var stream = File.Create(savePath); await cmd.Document.CopyToAsync(stream, ct); return await ctx.WriteHtmxPage( new UploadPage(af, ctx, uploadedFileName: safeName), title: "Upload"); } } ```