Combobox
A multi-select combobox with optional input filtering. Displays selected values as badges with deselect capability.
Demo
Selected: None
Features
- Multi-select with badge display
- Optional input for filtering (list autocomplete)
- Two modes: editable (with input) or select-only (trigger-based)
- Deselect via badges with DeselectAsync delegate
- Full keyboard navigation
- Grouped items with labels
- WCAG compliant with aria-multiselectable="true"
Installation
dotnet add package SummitUIAnatomy
Import the components and structure them as follows:
<SmComboboxRoot TValue="string" @bind-Values="selectedValues">
<SmComboboxTrigger TValue="string">
<SmComboboxSelectedValues TValue="string" Context="items">
@foreach (var item in items)
{
<span class="badge">
@item.Label
<button @onclick="item.DeselectAsync">x</button>
</span>
}
</SmComboboxSelectedValues>
<SmComboboxInput TValue="string" Placeholder="Search..." />
</SmComboboxTrigger>
<SmComboboxPortal TValue="string">
<SmComboboxContent TValue="string">
<SmComboboxViewport TValue="string">
<SmComboboxItem TValue="string" Value="@("option-1")" Label="Option 1">
Option 1
</SmComboboxItem>
</SmComboboxViewport>
<SmComboboxEmpty TValue="string">No results found</SmComboboxEmpty>
</SmComboboxContent>
</SmComboboxPortal>
</SmComboboxRoot>Sub-components
ComboboxRoot<TValue>
Generic root container managing multi-select state.
ComboboxTrigger<TValue>
Container for badges and input.
ComboboxInput<TValue>
Text input for filtering with combobox role.
ComboboxSelectedValues<TValue>
Exposes selected items for badge rendering.
ComboboxPortal<TValue>
Renders content in fixed-position container.
ComboboxContent<TValue>
Floating listbox with multi-select support.
ComboboxViewport<TValue>
Scrollable container for items.
ComboboxItem<TValue>
Selectable option with toggle behavior.
ComboboxEmpty<TValue>
Shown when no items match filter.
ComboboxClear<TValue>
Button to clear all selections.
ComboboxGroup<TValue>
Groups related items together.
ComboboxGroupLabel
Label for a group of items.
API Reference
ComboboxRoot<TValue>
The root component that manages multi-select state and provides cascading context.
| Property | Type | Default | Description |
|---|---|---|---|
| Values | IReadOnlyCollection<TValue> | [] | Controlled selected values |
| DefaultValues | IReadOnlyCollection<TValue>? | null | Default values (uncontrolled) |
| ValuesChanged | EventCallback<IReadOnlyCollection<TValue>> | - | Values change callback |
| Open | bool? | null | Controlled open state |
| DefaultOpen | bool | false | Default open state |
| OpenChanged | EventCallback<bool> | - | Open state change callback |
| Disabled | bool | false | Disable entire combobox |
| Required | bool | false | For form validation |
| Invalid | bool | false | For error styling |
ComboboxTrigger<TValue>
Container that holds badges and input. Has combobox role in select-only mode.
| Property | Type | Default | Description |
|---|---|---|---|
| As | string | "div" | HTML element to render |
| AriaLabel | string? | null | Direct aria-label |
| AriaLabelledBy | string? | null | ID of external label |
ComboboxInput<TValue>
Text input for filtering with combobox role and aria-autocomplete.
| Property | Type | Default | Description |
|---|---|---|---|
| Placeholder | string? | null | Placeholder text |
| AriaLabel | string? | null | Direct aria-label |
| AriaLabelledBy | string? | null | ID of external label |
ComboboxSelectedValues<TValue>
Exposes selected items with DeselectAsync delegate for badge rendering.
| Property | Type | Default | Description |
|---|---|---|---|
| ChildContentrequired | RenderFragment<IReadOnlyList<ComboboxSelectedItem<TValue>>> | - | Template for rendering selected items |
ComboboxContent<TValue>
Floating listbox panel with multi-select support.
| Property | Type | Default | Description |
|---|---|---|---|
| As | string | "div" | HTML element to render |
| Side | Side | Bottom | Placement side |
| SideOffset | int | 4 | Offset from trigger (px) |
| Align | Align | Start | Alignment along side axis |
| AlignOffset | int | 0 | Alignment offset (px) |
| AvoidCollisions | bool | true | Avoid viewport boundaries |
| CollisionPadding | int | 8 | Viewport padding (px) |
| EscapeKeyBehavior | EscapeKeyBehavior | Close | Escape key behavior |
| OutsideClickBehavior | OutsideClickBehavior | Close | Outside click behavior |
ComboboxItem<TValue>
Selectable option that can be toggled on/off.
| Property | Type | Default | Description |
|---|---|---|---|
| Valuerequired | TValue | - | Value of this item |
| Key | string? | null | Optional string key for identification |
| Label | string? | null | Label for filtering and display |
| Disabled | bool | false | Disable item |
| OnSelectionChange | EventCallback<bool> | - | Selection change callback |
ComboboxSelectedItem Model
Each selected item is exposed via the ComboboxSelectedValues component with the following properties:
public sealed class ComboboxSelectedItem<TValue> where TValue : notnull
{
public TValue Value { get; } // The actual value
public string Label { get; } // Display label
public Func<Task> DeselectAsync { get; } // Delegate to remove from selection
}Examples
Basic Multi-Select with Badges
@code {
private IReadOnlyCollection<string> selectedFruits = Array.Empty<string>();
}
<SmComboboxRoot TValue="string" @bind-Values="selectedFruits">
<SmComboboxTrigger TValue="string" class="combobox-trigger">
<SmComboboxSelectedValues TValue="string" Context="items">
@foreach (var item in items)
{
<span class="badge">
@item.Label
<button type="button" @onclick="item.DeselectAsync">x</button>
</span>
}
</SmComboboxSelectedValues>
<SmComboboxInput TValue="string" Placeholder="Search fruits..." />
</SmComboboxTrigger>
<SmComboboxPortal TValue="string">
<SmComboboxContent TValue="string" class="combobox-content">
<SmComboboxViewport TValue="string">
<SmComboboxItem TValue="string" Value="@("apple")" Label="Apple">Apple</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("banana")" Label="Banana">Banana</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("cherry")" Label="Cherry">Cherry</SmComboboxItem>
</SmComboboxViewport>
<SmComboboxEmpty TValue="string">No fruits found</SmComboboxEmpty>
</SmComboboxContent>
</SmComboboxPortal>
</SmComboboxRoot>
<p>Selected: @string.Join(", ", selectedFruits)</p>Select-Only Mode (No Input)
When no ComboboxInput is present, the trigger gets the combobox role.
@* Select-only mode: no ComboboxInput, trigger gets combobox role *@
<SmComboboxRoot TValue="string" @bind-Values="selectedTags">
<SmComboboxTrigger TValue="string" class="combobox-trigger-button">
<SmComboboxSelectedValues TValue="string" Context="items">
@if (items.Count == 0)
{
<span class="placeholder">Select tags...</span>
}
else
{
<span>@items.Count selected</span>
}
</SmComboboxSelectedValues>
<span class="chevron">▼</span>
</SmComboboxTrigger>
<SmComboboxPortal TValue="string">
<SmComboboxContent TValue="string" class="combobox-content">
<SmComboboxViewport TValue="string">
<SmComboboxItem TValue="string" Value="@("important")" Label="Important">
<span class="checkbox">☐</span> Important
</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("urgent")" Label="Urgent">
<span class="checkbox">☐</span> Urgent
</SmComboboxItem>
</SmComboboxViewport>
</SmComboboxContent>
</SmComboboxPortal>
</SmComboboxRoot>Grouped Items
Organize items into logical groups.
<SmComboboxRoot TValue="string" @bind-Values="selectedFood">
<SmComboboxTrigger TValue="string" class="combobox-trigger">
<SmComboboxSelectedValues TValue="string" Context="items">
@foreach (var item in items)
{
<span class="badge">@item.Label</span>
}
</SmComboboxSelectedValues>
<SmComboboxInput TValue="string" Placeholder="Search food..." />
</SmComboboxTrigger>
<SmComboboxPortal TValue="string">
<SmComboboxContent TValue="string" class="combobox-content">
<SmComboboxViewport TValue="string">
<SmComboboxGroup TValue="string">
<SmComboboxGroupLabel class="group-label">Fruits</SmComboboxGroupLabel>
<SmComboboxItem TValue="string" Value="@("apple")" Label="Apple">Apple</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("banana")" Label="Banana">Banana</SmComboboxItem>
</SmComboboxGroup>
<SmComboboxGroup TValue="string">
<SmComboboxGroupLabel class="group-label">Vegetables</SmComboboxGroupLabel>
<SmComboboxItem TValue="string" Value="@("carrot")" Label="Carrot">Carrot</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("broccoli")" Label="Broccoli">Broccoli</SmComboboxItem>
</SmComboboxGroup>
</SmComboboxViewport>
<SmComboboxEmpty TValue="string">No food found</SmComboboxEmpty>
</SmComboboxContent>
</SmComboboxPortal>
</SmComboboxRoot>With Clear Button
Use ComboboxClear to add a button that clears all selections.
<SmComboboxRoot TValue="string" @bind-Values="selectedValues">
<SmComboboxTrigger TValue="string" class="combobox-trigger">
<SmComboboxSelectedValues TValue="string" Context="items">
@foreach (var item in items)
{
<span class="badge">@item.Label</span>
}
</SmComboboxSelectedValues>
<SmComboboxInput TValue="string" Placeholder="Search..." />
<SmComboboxClear TValue="string" class="clear-button">
<svg>...</svg> @* Clear icon *@
</SmComboboxClear>
</SmComboboxTrigger>
...
</SmComboboxRoot>Disabled State
Disable entire combobox or individual items.
@* Disabled entire combobox *@
<SmComboboxRoot TValue="string" Disabled="true">
<SmComboboxTrigger TValue="string" class="combobox-trigger">
<SmComboboxInput TValue="string" Placeholder="Disabled..." />
</SmComboboxTrigger>
...
</SmComboboxRoot>
@* Disabled individual items *@
<SmComboboxRoot TValue="string" @bind-Values="selectedValues">
<SmComboboxTrigger TValue="string" class="combobox-trigger">
<SmComboboxInput TValue="string" Placeholder="Select..." />
</SmComboboxTrigger>
<SmComboboxPortal TValue="string">
<SmComboboxContent TValue="string" class="combobox-content">
<SmComboboxViewport TValue="string">
<SmComboboxItem TValue="string" Value="@("option1")" Label="Option 1">Option 1</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("option2")" Label="Option 2" Disabled="true">
Option 2 (Disabled)
</SmComboboxItem>
<SmComboboxItem TValue="string" Value="@("option3")" Label="Option 3">Option 3</SmComboboxItem>
</SmComboboxViewport>
</SmComboboxContent>
</SmComboboxPortal>
</SmComboboxRoot>Styling
Data Attributes
| Attribute | Values | Description |
|---|---|---|
| data-state | "open" | "closed" | Dropdown open state (on trigger/content/input) |
| data-state | "checked" | "unchecked" | Item selection state |
| data-highlighted | Present when focused | Item is keyboard-focused |
| data-selected | Present when selected | Item is in the selection set |
| data-disabled | Present when disabled | Item or combobox is disabled |
| data-placeholder | Present when no values | No items selected yet |
CSS Example
.combobox-trigger {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
min-width: 280px;
padding: 6px 8px;
background: white;
border: 1px solid #ccc;
border-radius: 6px;
cursor: text;
}
.combobox-trigger:focus-within {
border-color: #0066cc;
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
}
.combobox-trigger[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
[data-summit-combobox-input] {
flex: 1;
min-width: 100px;
border: none;
outline: none;
background: transparent;
}
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: #e6f0ff;
border-radius: 4px;
font-size: 12px;
}
.combobox-content {
background: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
}
[data-summit-combobox-item] {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
}
[data-summit-combobox-item][data-highlighted] {
background: #f5f5f5;
}
[data-summit-combobox-item][data-selected] {
background: #e6f0ff;
}
[data-summit-combobox-item][data-disabled] {
opacity: 0.5;
cursor: not-allowed;
}
[data-summit-combobox-empty] {
padding: 8px 12px;
color: #666;
font-style: italic;
}Accessibility
Keyboard Navigation
| Key | Action |
|---|---|
| Enter | Toggle highlighted item selection (dropdown stays open) |
| Space | Toggle item (select-only mode) or type space (input mode) |
| ArrowDown | Open dropdown or move to next item |
| ArrowUp | Open dropdown or move to previous item |
| Ctrl+Home | Move to first item (input mode) |
| Ctrl+End | Move to last item (input mode) |
| Escape | Close dropdown |
| Backspace | Remove last selected value (when input is empty) |
ARIA Attributes
- ComboboxInput:
Has
role="combobox",aria-autocomplete="list", andaria-expanded - ComboboxTrigger (select-only):
Has
role="combobox"when no input is present - ComboboxContent:
Has
role="listbox"witharia-multiselectable="true" - ComboboxItem:
Has
role="option"witharia-selected