Files
Htmx/docs/Components/Select.md
T
2026-05-05 23:55:26 +05:00

6.7 KiB
Raw Blame History

Select

A styled dropdown that lets the user pick one option from a list. Use it for things like country selection, category filters, or anything where the user chooses from a fixed set of values.


Quick example

new Select(
    id:    "country",
    name:  "country",
    label: "Country",
    options: new[]
    {
        ("us", "United States"),
        ("gb", "United Kingdom"),
        ("ca", "Canada"),
    },
    selectedValue: "us")

All the options

public Select(
    string id,
    IEnumerable<(string Value, string Display)> options,
    string? selectedValue = null,
    string name           = "",
    string label          = "",
    string description    = "",
    string extraClasses   = "",
    string hxAttrs        = "")
Parameter What it does
id The element id. Also used by the <label for="...">.
options The list of choices. Each is a (Value, Display) tuple.
selectedValue The Value of the option to pre-select. Leave null to show the first option.
name Form field name — required if you want the value submitted.
label Visible text label above the dropdown.
description Small hint text below the field.
extraClasses Additional Tailwind classes on the <select> element.
hxAttrs Extra HTML attributes appended verbatim — use for HTMX and data-*.

Real-world examples

Category filter that reloads the list on change

new Select(
    id:      "category",
    name:    "category",
    label:   "Filter by category",
    options: categories.Select(c => (c.Slug, c.Name)),
    selectedValue: currentCategory,
    hxAttrs: """hx-get="/products" hx-target="#product-list" hx-trigger="change"""")

Dynamic options from the database (with current value pre-selected)

var options = roles.Select(r => (r.Id.ToString(), r.Name));

new Select(
    id:            "role",
    name:          "roleId",
    label:         "Role",
    options:       options,
    selectedValue: user.RoleId.ToString())

Reading on the server:

public record Command([property: FromForm] string RoleId);

Simple yes/no choice

new Select(
    id:    "active",
    name:  "isActive",
    label: "Status",
    options: new[]
    {
        ("true",  "Active"),
        ("false", "Inactive"),
    },
    selectedValue: user.IsActive ? "true" : "false")

How it works

Select renders a standard <select> element — no custom dropdown JavaScript. The browser's native dropdown is used, which is the most accessible and reliable approach. The selected option is matched by Value and has selected="selected" set on render. hxAttrs: """hx-get="/cities" hx-target="#city-select" hx-trigger="change" hx-include="[name='region']"""")


### Reading in a form handler

```csharp
public record Command([property: FromForm] string Country);

// command.Country == "us" | "gb" | "ca" | "au"

Placeholder option (no pre-selection)

new Select(
    id:   "role",
    name: "role",
    label: "Role",
    options: new[]
    {
        ("",      "— Select a role —"),
        ("admin", "Administrator"),
        ("user",  "Regular user"),
    },
    selectedValue: "")

Tips and tricks

  • Pass an empty-value placeholder as the first option (("", "Select…")) to force the user to make an explicit selection.
  • selectedValue comparison is exact — make sure the value you pass matches one of the Value strings in options.
  • hxAttrs is verbatim — you can add multiple, size, disabled, autocomplete, or any other native attribute here.
  • To conditionally disable individual options, build the raw <select> HTML manually or subclass the component.
  • To conditionally disable individual options, build the raw <select> HTML manually or subclass the component.

Complete page example

Templates/FilterProductsPage.htmx

<div class="max-w-4xl mx-auto py-10">
  <h1 class="text-2xl font-bold mb-6">Products</h1>
  <form class="flex gap-4 mb-8 items-end"
        hx-get="/products/filter"
        hx-target="#product-list"
        hx-trigger="change">
    $$CategorySelect$$
    $$SortSelect$$
  </form>
  <div id="product-list">
    $$ProductTable$$
  </div>
</div>

Templates/FilterProductsPage.htmx.cs

namespace Htmx.ApiDemo.Templates;

public sealed class FilterProductsPage : FilterProductsPageBase
{
    private readonly IHtmxComponent _category;
    private readonly IHtmxComponent _sort;
    private readonly IHtmxComponent _table;

    public FilterProductsPage(
        IEnumerable<Product> products,
        string selectedCategory = "",
        string selectedSort     = "name-asc")
    {
        _category = new Components.Select(
            id:   "category",
            name: "category",
            label: "Category",
            options: new[]
            {
                ("",           "All categories"),
                ("electronics","Electronics"),
                ("clothing",   "Clothing"),
                ("books",      "Books"),
            },
            selectedValue: selectedCategory);

        _sort = new Components.Select(
            id:   "sort",
            name: "sort",
            label: "Sort by",
            options: new[]
            {
                ("name-asc",   "Name AZ"),
                ("name-desc",  "Name ZA"),
                ("price-asc",  "Price: low to high"),
                ("price-desc", "Price: high to low"),
            },
            selectedValue: selectedSort);

        _table = new Components.Table(
            headers: new[] { "Name", "Category", "Price" },
            rows: products.Select(p => new[]
            {
                System.Net.WebUtility.HtmlEncode(p.Name),
                System.Net.WebUtility.HtmlEncode(p.Category),
                $"${p.Price:F2}",
            }));
    }

    protected override void RenderCategorySelect(HtmxRenderContext ctx) => _category.Render(ctx.Next());
    protected override void RenderSortSelect(HtmxRenderContext ctx)     => _sort.Render(ctx.Next());
    protected override void RenderProductTable(HtmxRenderContext ctx)   => _table.Render(ctx.Next());
}

GET handler (full page + HTMX partial)

[Handler]
[MapGet("/products")]
public static partial class GetProductsHandler
{
    public record Query(
        [property: FromQuery] string Category = "",
        [property: FromQuery] string Sort     = "name-asc");

    private static async Task<IResult> HandleAsync(
        Query q, HttpContext ctx, ProductService products, CancellationToken ct)
    {
        var items = await products.FilterAsync(q.Category, q.Sort, ct);
        return await ctx.WriteHtmxPage(
            new FilterProductsPage(items, q.Category, q.Sort), title: "Products");
    }
}