Cascading selects
Select A's value determines select B's options. Changing A re-renders B from the server.
#When to use
Country → state, region → zone, project → environment, team → member. Any dependent dropdown where B's options are too many to ship up front.
#Markup
<select name="country"
hx-get="/regions/states"
hx-trigger="change"
hx-target="#state_select"
hx-swap="outerHTML">
<option value="US">United States</option>
<option value="CA">Canada</option>
<option value="MX">Mexico</option>
</select>
<select id="state_select" name="state">
<partial name="_StateOptions" model="Model.States" />
</select>
hx-include="this" is implicit — the changed 's value is sent as a query string parameter (?country=CA).
#Handler
public async Task<IActionResult> OnGetAsync(string country = "US", CancellationToken ct = default)
{
var states = await _regions.StatesForAsync(country, ct);
return this.PageOrPartial("_StateSelect", states);
}
#The partial
@model IEnumerable<Region>
<select id="state_select" name="state">
@foreach (var r in Model)
{
<option value="@r.Code">@r.Name</option>
}
</select>
The partial includes the id and name attributes so the htmx swap replaces the existing cleanly.
#Preserve the current value
If you're editing an existing record, pass the selected state via ViewData and emit selected on the matching :
<option value="@r.Code" selected="@(r.Code == ViewData["State"] ? "selected" : null)">@r.Name</option>
#Related
- Async autocomplete — for dependent dropdowns with large option sets.