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

239 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```csharp
new Select(
id: "country",
name: "country",
label: "Country",
options: new[]
{
("us", "United States"),
("gb", "United Kingdom"),
("ca", "Canada"),
},
selectedValue: "us")
```
---
## All the options
```csharp
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
```csharp
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)
```csharp
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:
```csharp
public record Command([property: FromForm] string RoleId);
```
### Simple yes/no choice
```csharp
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)
```csharp
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`**
```html
<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`**
```csharp
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)**
```csharp
[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");
}
}
```