DropdownControl

June 6, 2026 · View on GitHub

A dropdown/combobox control that displays a list of selectable items with keyboard navigation, type-ahead search, and custom item formatting.

Overview

The DropdownControl renders a single-line header showing the prompt and the currently selected item, followed by a triangle indicator. Activating the control expands a scrollable list of items rendered as a portal overlay, so the list can extend beyond the bounds of the parent container without affecting the control's own height (which stays constant at one line plus margins).

The dropdown supports full keyboard navigation (arrow keys, Home/End, Page Up/Down), type-ahead search (type the first letters of an item to jump to it), and mouse interaction including hover highlighting, click-to-select, and wheel scrolling. The open list auto-flips upward when there is not enough room below the header. Items can carry an optional icon, an icon color, a programmatic Value, and an arbitrary Tag for associating user data.

Selection is exposed three ways — by index (SelectedIndex), by item (SelectedItem), and by text/value (SelectedValue) — each with its own change event. A custom ItemFormatter delegate lets you control how each item is rendered in the open list.

See also: ListControl

Quick Start

var dropdown = Controls.Dropdown("Choose a fruit:")
    .AddItems("Apple", "Banana", "Cherry")
    .SelectedIndex(0)
    .OnSelectionChanged((s, index) =>
    {
        // index is the newly selected index
    })
    .Build();

window.AddControl(dropdown);

Builder API

Create a DropdownBuilder through the Controls factory:

var builder = Controls.Dropdown("Select...");

Item Methods

.WithPrompt(string prompt)                        // Set the header prompt text
.AddItem(string text, string? value = null, Color? color = null)  // Add an item with optional value and icon color
.AddItem(DropdownItem item)                       // Add a pre-built DropdownItem
.AddItems(params string[] items)                  // Add multiple string items
.AddItems(IEnumerable<DropdownItem> items)        // Add multiple DropdownItems
.SelectedIndex(int index)                         // Set the initially selected index

Layout Methods

.WithAlignment(HorizontalAlignment align)         // Horizontal alignment
.WithVerticalAlignment(VerticalAlignment align)   // Vertical alignment
.WithMargin(int left, int top, int right, int bottom)
.WithMargin(int margin)                           // Uniform margin on all sides
.WithWidth(int width)                             // Fixed width
.Visible(bool visible = true)                     // Initial visibility

Identity Methods

.WithName(string name)                            // Name for FindControl<T>() lookups
.WithTag(object tag)                              // Arbitrary user data
.WithStickyPosition(StickyPosition pos)           // Sticky positioning
.StickyTop()                                      // Shorthand for StickyPosition.Top
.StickyBottom()                                   // Shorthand for StickyPosition.Bottom

Event Methods

.OnSelectionChanged(EventHandler<int> handler)
.OnSelectionChanged(WindowEventHandler<int> handler)              // includes Window reference
.OnSelectedItemChanged(EventHandler<DropdownItem?> handler)
.OnSelectedItemChanged(WindowEventHandler<DropdownItem?> handler) // includes Window reference
.OnSelectedValueChanged(EventHandler<string?> handler)
.OnSelectedValueChanged(WindowEventHandler<string?> handler)      // includes Window reference
.OnGotFocus(EventHandler handler)
.OnGotFocus(WindowEventHandler<EventArgs> handler)
.OnLostFocus(EventHandler handler)
.OnLostFocus(WindowEventHandler<EventArgs> handler)

Building

DropdownControl control = builder.Build();

// Implicit conversion is also supported:
DropdownControl control = Controls.Dropdown("Pick one:")
    .AddItems("A", "B", "C");

Properties

PropertyTypeDefaultDescription
Promptstring"Select an item:"Prompt text displayed in the header before the selected item.
ItemsList<DropdownItem>emptyThe list of dropdown items.
StringItemsList<string>emptyThe items as plain strings; getting projects Text, setting replaces all items.
SelectedIndexint-1Index of the selected item, or -1 if none.
SelectedItemDropdownItem?nullThe selected item, or null if none.
SelectedValuestring?nullText or Value of the selected item, or null if none.
MaxVisibleItemsint5Maximum items shown in the open list before scrolling (clamped to a minimum of 1).
IsDropdownOpenboolfalseWhether the list is currently expanded. Setting it opens/closes the portal.
IsEnabledbooltrueWhether the control accepts input.
AutoAdjustWidthbooltrueWhether the control auto-sizes its width to fit content.
ItemFormatterItemFormatterEvent?nullDelegate to customize how each item is rendered in the open list.
BackgroundColorColor?nullHeader background (unfocused); inherits from container/theme when null.
ForegroundColorColortheme ButtonForegroundColor (Color.White)Header foreground (unfocused).
FocusedBackgroundColorColortheme ButtonFocusedBackgroundColor (Color.Blue)Header background when focused.
FocusedForegroundColorColortheme ButtonFocusedForegroundColor (Color.White)Header foreground when focused.
HighlightBackgroundColorColortheme DropdownHighlightBackgroundColor (Color.Blue)Background of the highlighted/selected item in the list.
HighlightForegroundColorColortheme DropdownHighlightForegroundColor (Color.White)Foreground of the highlighted/selected item in the list.
HasFocusboolRead-only. Whether the control currently has focus.
CanReceiveFocusboolRead-only. True when IsEnabled.
ContentHeightint?Read-only. Rendered height (one line plus top/bottom margins).
WantsMouseEventsboolRead-only. True when IsEnabled.
CanFocusWithMouseboolRead-only. True when IsEnabled.

Events

EventArgumentsDescription
SelectedIndexChangedEventHandler<int>Fires when the selected index changes.
SelectedIndexChangedAsyncAsyncEventHandler<int>Async counterpart of SelectedIndexChanged.
SelectedItemChangedEventHandler<DropdownItem?>Fires when the selected item changes.
SelectedItemChangedAsyncAsyncEventHandler<DropdownItem?>Async counterpart of SelectedItemChanged.
SelectedValueChangedEventHandler<string?>Fires when the selected value (text or Value) changes.
SelectedValueChangedAsyncAsyncEventHandler<string?>Async counterpart of SelectedValueChanged.
GotFocusEventHandlerFires when the control receives focus.
LostFocusEventHandlerFires when the control loses focus (also closes the list).
MouseClickEventHandler<MouseEventArgs>Fires on a header or item click.
MouseRightClickEventHandler<MouseEventArgs>Fires when the header is right-clicked.
MouseEnterEventHandler<MouseEventArgs>Fires when the mouse enters the control.
MouseLeaveEventHandler<MouseEventArgs>Fires when the mouse leaves the control.
MouseMoveEventHandler<MouseEventArgs>Fires when the mouse moves over the control.
MouseDoubleClickEventHandler<MouseEventArgs>Declared for the IMouseAwareControl contract.

Keyboard Support

The dropdown ignores keys combined with Shift, Alt, or Ctrl.

KeyAction
EnterWhen closed, opens the list; when open, selects the highlighted item and closes
EscapeCloses the list without changing the selection
Down ArrowWhen closed, opens the list; when open, moves the highlight down one item
Up ArrowWhen open, moves the highlight up one item
HomeWhen open, highlights the first item
EndWhen open, highlights the last item
Page UpWhen open, moves the highlight up by MaxVisibleItems
Page DownWhen open, moves the highlight down by MaxVisibleItems
Letters / digitsWhen open, type-ahead: jumps to the first item whose text starts with the typed sequence (resets after ~1.5 s of inactivity)

Mouse Support

  • Click the header toggles the list open or closed and focuses the control.
  • Hover over an item highlights it (HighlightBackgroundColor/HighlightForegroundColor).
  • Click an item selects it and closes the list.
  • Mouse wheel over the open list scrolls items up or down when there are more than MaxVisibleItems.
  • Click the scroll-indicator row (the / arrows) scrolls the list up or down.
  • Click outside the open list dismisses it.
  • Right-click the header raises MouseRightClick.

Each item in the list is a DropdownItem:

PropertyTypeDefaultDescription
Textstring(constructor)Display text for the item.
Iconstring?nullOptional icon character/string shown before the text.
IconColorColor?nullOptional color for the icon.
Valuestring?nullProgrammatic value returned by SelectedValue instead of Text when set.
Tagobject?nullArbitrary user data.
IsEnabledbooltrueWhether the item can be selected.

A string converts implicitly to a DropdownItem, so dropdown.AddItem("Apple") and dropdown.Items.Add("Apple") both work.

var item = new DropdownItem("Japanese", icon: "●", iconColor: Color.Red)
{
    Value = "jp",
    Tag = someUserObject
};

Examples

Simple Dropdown

var dropdown = Controls.Dropdown("Pick a color:")
    .AddItems("Red", "Green", "Blue")
    .SelectedIndex(0)
    .OnSelectionChanged((s, index) =>
    {
        logService.LogInfo($"Selected index {index}");
    })
    .Build();

window.AddControl(dropdown);

Items with Values and Icons

var cuisine = Controls.Dropdown("Choose cuisine...")
    .AddItem(new DropdownItem("Italian", "●", Color.Green) { Value = "it" })
    .AddItem(new DropdownItem("Mexican", "●", Color.Orange1) { Value = "mx" })
    .AddItem(new DropdownItem("Thai", "●", Color.Magenta1) { Value = "th" })
    .SelectedIndex(0)
    .OnSelectedValueChanged((s, value) =>
    {
        // value is "it", "mx", or "th"
    })
    .Build();

window.AddControl(cuisine);

Reacting to the Selected Item

var dropdown = Controls.Dropdown("Choose diet...")
    .AddItems("None", "Vegetarian", "Vegan", "Gluten-Free", "Keto")
    .SelectedIndex(0)
    .OnSelectedItemChanged((s, item) =>
    {
        string text = item?.Text ?? "None";
        // Use item.Tag, item.Value, etc.
    })
    .Build();

window.AddControl(dropdown);

Limiting Visible Items and Scrolling

var dropdown = Controls.Dropdown("Choose servings...")
    .AddItems("1 person", "2 people", "3 people", "4 people",
              "6 people", "8 people")
    .SelectedIndex(2)
    .Build();

// Show only 3 items at a time; the rest scroll
dropdown.MaxVisibleItems = 3;

window.AddControl(dropdown);

Custom Item Formatting

var dropdown = Controls.Dropdown("Status:")
    .AddItems("Active", "Pending", "Closed")
    .Build();

dropdown.ItemFormatter = (item, isSelected, hasFocus) =>
    isSelected ? $"[bold]{item.Text}[/]" : item.Text;

window.AddControl(dropdown);

Linked Dropdowns with a Live Summary

var summary = Controls.Markup("[dim]No selection[/]").Build();

var cuisine = Controls.Dropdown("Cuisine...")
    .AddItems("Japanese", "Italian", "Mexican")
    .SelectedIndex(0)
    .Build();

var spice = Controls.Dropdown("Spice...")
    .AddItems("Mild", "Medium", "Hot")
    .SelectedIndex(0)
    .Build();

void Update(object? s, int _)
{
    summary.SetContent(new List<string>
    {
        $"Cuisine: {cuisine.SelectedItem?.Text}",
        $"Spice:   {spice.SelectedItem?.Text}",
    });
}

cuisine.SelectedIndexChanged += Update;
spice.SelectedIndexChanged += Update;

window.AddControl(cuisine);
window.AddControl(spice);
window.AddControl(summary);

Best Practices

  1. Set an initial selection with .SelectedIndex(0) so the header shows a meaningful value instead of (None).
  2. Use Value for stable keys when item display text may change or be localized — read it back through SelectedValue or OnSelectedValueChanged.
  3. Tune MaxVisibleItems for long lists to keep the overlay compact; the control scrolls automatically once items exceed the limit.
  4. Pick the right change event: use OnSelectionChanged for index-based logic, OnSelectedItemChanged to access Tag/Icon, and OnSelectedValueChanged for value-keyed handling.
  5. Mutate items and selection on the UI thread — wrap changes from background work in windowSystem.EnqueueOnUIThread(...).
  6. Use the WindowEventHandler overloads when a handler needs access to the parent Window without capturing it manually.

See Also


Back to Controls | Back to Main Documentation