#
Value Select
#
Implementing Cascading Selects with htmx and ASP.NET Core
The Cascading Selects pattern (also known as dependent selects) is a common UI requirement where the options in one dropdown list depend on the selection made in a previous dropdown. htmx makes this incredibly simple by allowing you to fetch and swap the dependent dropdown's content asynchronously.
#
The Frontend: Razor & htmx
In Index.cshtml, we have two select elements. The first select (Make) is configured to trigger an htmx request whenever its value changes.
Index.cshtml
<div>
<label class="control-label">Make</label>
<select name="make"
hx-get="@Url.Page("Index", "Models")"
hx-target="#models"
hx-indicator=".htmx-indicator">
@foreach (var make in Model.ManufacturerMake)
{
<option value="@make">@make</option>
}
</select>
</div>
<div>
<label class="control-label">Model</label>
<select id="models" name="model">
<partial name="_modelSelector" model="@Model.ManufacturerModels"/>
</select>
</div>
Key htmx attributes used:
hx-get: Initiates a GET request to theModelshandler when the user selects a different "Make".hx-target="#models": Specifies that the HTML returned by the server (the new list of options) should be placed inside the element with the IDmodels.name="make": htmx automatically includes the value of the select element in the request as a query parameter namedmake.
#
The Backend: C# PageModel
The IndexModel manages the data for the manufacturers and their corresponding models. It uses a Dictionary to store the relationships and provides a handler to return the filtered models.
Index.cshtml.cs
public class IndexModel : PageModel
{
private static readonly Dictionary<string, List<string>> MakeModel = new()
{
{ "Audi", new() { "A1", "A4", "A6" } },
{ "Toyota", new() { "Landcruiser", "Tacoma", "Yaris" } },
{ "BMW", new() { "325i", "325ix", "X5" } }
};
public List<string> ManufacturerMake { get; set; } = new();
public List<string> ManufacturerModels { get; set; } = new();
[FromQuery(Name = "make")]
public string Make { get; set; } = string.Empty;
public void OnGet()
{
ManufacturerMake = MakeModel.Keys.ToList();
Make = ManufacturerMake.First();
ManufacturerModels = MakeModel[Make];
}
// This handler returns the partial view for the second select
public PartialViewResult OnGetModels()
{
ManufacturerModels = MakeModel.ContainsKey(Make)
? MakeModel[Make]
: new List<string>();
return Partial("_modelSelector", ManufacturerModels);
}
}
#
The Result: Partial View
The _modelSelector.cshtml partial view simply renders the <option> elements for the model dropdown.
_modelSelector.cshtml
@model List<string>
<option value="">Select a model</option>
@foreach (var s in Model)
{
<option value="@s">@s</option>
}
#
Why this works well
- Cleaner Logic: You don't need to ship a massive JSON object containing every possible combination of data to the client.
- Reduced Complexity: There is no need for custom JavaScript to clear, filter, or rebuild the second dropdown.
- Server-Side Control: The server remains the source of truth for the available options, making it easy to integrate with a database or external API.
- Instant Feedback: The UI updates immediately upon selection, providing a smooth and responsive experience for the user.