Combobox
Component

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

bash
dotnet add package SummitUI

Anatomy

Import the components and structure them as follows:

razor
<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:

csharp
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

razor
@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.

razor
@* 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.

razor
<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.

razor
<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.

razor
@* 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

css
.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", and aria-expanded
  • ComboboxTrigger (select-only): Has role="combobox" when no input is present
  • ComboboxContent: Has role="listbox" with aria-multiselectable="true"
  • ComboboxItem: Has role="option" with aria-selected
An unhandled error has occurred. Reload X