Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ComboBox

ComboBox preview

ComboBox — dropdown selection widget.

Public functions

ComboBox

Detailed description

Generic over the item type T: Clone + PartialEq + 'static. Selection is value-based: the bound Signal<Option<T>> survives reorder and insertion of the backing model. Items come from one of four input paths:

  • ComboBox::new — static list of localizable strings (the 90% case).
  • ComboBox::from_items — static list of typed values.
  • ComboBox::from_model — reactive ListModel<T>.
  • ComboBox::from_source — external ListDataSource<Item = T>.

The dropdown panel's node is created during build() and kept dormant, but its subtree is deferred — built the first time the combo is opened.

Keyboard

The keys move a highlight through the list, and only a commit changes the value: the W3C ARIA select-only combobox pattern. A reader arrowing to hear the options changes nothing, and an application acting on the value (a language switcher) acts once, on the option the user picked.

  • Enter / Space: open the list; in the open list, commit the highlighted option and close.
  • ArrowDown / ArrowUp: open the list and move the highlight one item from the value, stopping at the ends; with no value, ArrowDown reaches the first item. Win32's combo box, QComboBox, GTK and the W3C ARIA listbox pattern all stop rather than wrap; a combo box is a value, and wrapping is the menu convention.
  • Alt+ArrowDown / Alt+ArrowUp: open the list on the value (or the first item) without moving, and close it. The Win32 / WinForms / WPF chord and the ARIA combobox pattern.
  • F4: toggle the list (Win32 / Qt / WPF).
  • Escape: close the list and keep the value.
  • Home / End: first / last item.
  • PageUp / PageDown: one page, where a page is max_visible_items rows.
  • Printable characters: type-ahead, within type_ahead_timeout.
  • A chord holding Ctrl, Alt or Super is not the combo box's and falls through to the application; Shift is, so a capital letter still types.

Every key that moves the highlight opens the list first when it is closed, so the reader hears the option it reaches. Focus stays on the combo box (or, in a searchable list, on its search field), which names the highlighted option as its active_descendant: every platform adapter follows that as the focus.

The widget is split across four internal modules:

  • state holds the ItemSource accessor, the default max_visible_items constant, and the index helpers.
  • item holds the single-row DropdownItem widget.
  • panel holds the DropdownPanel overlay content and the FilteredItemList inner widget.
  • tests holds the headless unit tests.

Touch and pen

The closed box is one target — the arrow column is paint inside it, not a second target — and it opens the dropdown from its tap, so already on the release. The field's hover tint is decoration with nothing behind it.

The dropdown's rows are menu rows and take the menu row's target floor at every density; before this they measured against the raw Compact constant while a MenuList's rows grew, and the panel that shows max_visible_items of them now resolves the same number they do.

Density

The picture above is the widget at TargetDensity::Compact, the mouse-and-keyboard ladder. Below is the same subject on the same canvas with only the ladder changed, so what moves is the density and nothing else — where the subject no longer fits, that is what the denser targets cost it at that size. See docs/density-and-targets.md.

Touch

ComboBox at Touch density

API reference

📖 Full rustdoc API for this module

pub struct ComboBox

Import: use teksilo::widgets::ComboBox;

A dropdown selection widget.

// Simple: list of strings.
let selected = ctx.signal(None::<String>);
ComboBox::new(["Apple", "Banana", "Cherry"], selected)
    .placeholder(lit!("Select a fruit..."))

// Typed items: any T: Clone + PartialEq, plus a label extractor.
#[derive(Clone, PartialEq)] struct Fruit { name: String, emoji: &'static str }
let selected = ctx.signal(None::<Fruit>);
ComboBox::from_items(fruits, selected)
    .item_label(|f: &Fruit| lit!(format!("{} {}", f.emoji, f.name)))

// Model-backed: reactive.
let model = ListModel::from_vec(fruits);
ComboBox::from_model(model, selected)
    .item_label(|f: &Fruit| lit!(f.name.clone()))
    .max_visible_items(6)
#![allow(unused)]
fn main() {
pub struct ComboBox<T: Clone + PartialEq + 'static> { /* fields */ }
}

Methods

pub fn new( items: impl IntoIterator<Item = impl Into<String>>, selected: Signal<Option<String>>, ) -> Self

Create a ComboBox from a list of strings.

Accepts any impl Into<String> — string literals (&str), owned Strings, resolved LocalizedStrings, etc. For translated items, resolve translations before passing in, e.g. vec![tr!(apple()).resolve_now(), ...].

pub fn from_items<F>( items: impl IntoIterator<Item = T>, selected: Signal<Option<T>>, item_label: F, ) -> Self where F: Fn(&T) -> LocalizedString + 'static,

Static list of typed items. item_label is the display extractor — it's required at construction so the compiler enforces it rather than a runtime check. For T = String, use ComboBox::new which defaults to the identity label.

pub fn from_model<F>(model: ListModel<T>, selected: Signal<Option<T>>, item_label: F) -> Self where F: Fn(&T) -> LocalizedString + 'static,

Backed by a reactive ListModel<T>. Inserts, removes, and reorders propagate into the dropdown automatically. If the currently-selected value disappears from the model, selected becomes None.

pub fn from_source<S, F>(source: S, selected: Signal<Option<T>>, item_label: F) -> Self where S: ListDataSource<Item = T> + 'static, F: Fn(&T) -> LocalizedString + 'static,

Backed by a custom ListDataSource — for external or paged data.

pub fn item_label(mut self, f: impl Fn(&T) -> LocalizedString + 'static) -> Self

Override the display-label extractor. Rarely needed — prefer passing item_label to the constructor. Useful for the ComboBox<String> path when you want a non-identity projection.

pub fn render_item(mut self, f: impl Fn(&T, bool) -> Box<dyn Widget> + 'static) -> Self

Custom cell rendering. The closure receives the item and a flag indicating whether it is the currently-selected value.

The framework wraps the returned widget with the correct Role::ListBoxOption accessibility and tap handler, so callers do not need to manage a11y or selection dispatch themselves.

Reactivity. The bool argument is a snapshot at build time. If the selection flips after the dropdown is open, the user's subtree is not automatically re-rendered; the framework-managed highlight background (behind the custom widget) does update, and closing and re-opening the dropdown picks up the new state. If you need a reactive appearance that tracks selection, close over a Signal<Option<T>> in your closure and compare against the item value inside a .map() / bind_* on primitives.

Accessibility. The wrapper's set_name(label) (from item_label) is what screen readers announce. If the returned widget includes its own text nodes (e.g. a bare TextWidget), the label may be announced twice — one from the wrapper, one from the inner text. Wrap primary text nodes in .a11y_hidden() to avoid duplication, and reserve visible widgets for presentation only.

pub fn render_selected(mut self, f: impl Fn(&T) -> Box<dyn Widget> + 'static) -> Self

Custom renderer for the trigger's selected value — the widget shown when the combo is closed. The parallel of render_item for the trigger rather than the dropdown rows.

When set, the closed combo shows f(&value) for the current selection instead of the plain text label (item_label). The canonical use is a FontPicker rendering the selected family name in its own typeface. The subtree is rebuilt whenever the selection changes and whenever the locale changes (so a None-state placeholder re-translates), without rebuilding the whole ComboBox.

Accessibility. The rendered subtree is excluded from the accessibility tree — the ComboBox's own accessibility(builder) already announces the selected value via set_value, so the custom visual can never double-announce. When nothing is selected the trigger shows the placeholder text.

pub fn on_select(mut self, f: impl Fn(&T, &mut EventContext) + 'static) -> Self

Register a callback fired when the user commits a selection — by tapping a dropdown row, or pressing Enter (or Space) on the option the keyboard highlighted. The callback receives the chosen value and a live EventContext, so it can run context-bearing actions that observing the bound selected signal cannot, such as ctx.set_locale(...), navigation, or opening another overlay.

Moving through the list (arrows, type-ahead, Home / End, the page keys) is not a commit and fires nothing, and neither does Enter on the option that is the value already. So a context change made here happens once, on the user's choice, never while they are still listening to the options (WCAG 3.2.2).

It fires only on user-driven commits, not on external writes to the selected signal (those are observed via ctx.effect). The selected signal is updated before the callback runs.

pub fn max_visible_items(mut self, n: usize) -> Self

Maximum number of items shown before the dropdown becomes scrollable. Defaults to 8. Clamped to at least 1.

pub fn type_ahead_timeout(mut self, d: Duration) -> Self

Reset window for keyboard type-ahead. Keystrokes more than d apart begin a fresh prefix; within d they extend it. Defaults to 500 ms, matching MenuList::type_ahead_timeout. Pass Duration::ZERO to treat each keystroke independently.

pub fn placeholder(mut self, text: impl Into<LocalizedString>) -> Self

Placeholder text shown in the trigger when selected is None. Accepts a tr!(...) directly (resolved at build); use placeholder_literal for an untranslated string.

pub fn label(mut self, label: impl Into<LocalizedString>) -> Self

Accessible label describing what this combo box is for (e.g. "Fruit", "Font family"). Independent of the visible placeholder and of the current selection — screen readers announce this as the name of the control.

pub fn enabled(mut self, enabled: impl Into<Prop<bool>>) -> Self

Set the enabled state, statically or reactively. Forwarded to the arena at build time.

pub fn variant(mut self, variant: ComboBoxVariant) -> Self

Pick a Tier-1 design-language variant (ComboBoxVariant::Outlined / Filled / Underline / Plain). The active ComboBoxStyle decides what to do with the hint — IntUI's default impl honours Outlined (default) and Plain; a custom impl (Material 3, macOS, etc.) might paint differently.

pub fn style(mut self, style: impl ComboBoxStyle) -> Self

Override the active ComboBoxStyle for this widget instance only. The default IntUI chrome (crate::styles::RecipeComboBoxStyle) resolves its dimensions from theme.input (the density InputTokens); custom impls can paint anything they want around the selected-label slot.

pub fn text_style(mut self, style: impl Into<teksilo_core::color_prop::TextStyleProp>) -> Self

Override the selected-value text style (font, size, weight). Accepts a TextStyleRole, a TextStyle, or a Signal of either. Default (unset) is TextStyleRole::Body.

pub fn text_role(mut self, color: impl Into<teksilo_core::color_prop::ColorProp>) -> Self

Override the selected-value text color. Accepts Color, a role, or a Signal of either. Default (unset) is enabled-derived (Primary / Disabled); setting this replaces that cascade.

pub fn tooltip(mut self, text: impl Into<LocalizedString>) -> Self

Attach a plain tooltip that appears after a hover delay. The tooltip is anchored to the trigger only — with the framework's overlay-boundary gate it does not re-trigger while the pointer is over the open dropdown's option rows.

Mutually exclusive with rich_tooltip / rich_tooltip_content / composite_tooltip — last call wins.

pub fn rich_tooltip(mut self, key: impl Into<String>) -> Self

Attach a rich tooltip resolved from the app-wide tooltip registry. The key is looked up via TooltipRegistry at build time; the resolved body supports inline markup, a shortcut chip, and a "more" disclosure. Overrides any previously set tooltip.

pub fn rich_tooltip_content(mut self, content: crate::tooltip::TooltipContent) -> Self

Attach a rich tooltip driven by inline TooltipContent — for one-off tooltips that aren't worth registering centrally. Overrides any previously set tooltip.

pub fn composite_tooltip(mut self, content: impl Widget + 'static) -> Self

Attach a composite tooltip — third tier, hosting an arbitrary widget tree (tabbed sections, charts, conditional rows). Promotes to a focusable Role::Dialog after the standard dwell. Overrides any plain or rich tooltip previously set.

Searchable-mode builders. The search field is a TextInput, which shares the RichTextEditor engine and therefore the teksilo-text dependency.

pub fn searchable(mut self, enabled: bool) -> Self

Show a search field at the top of the dropdown panel and filter the list live against the user's query. When true, items are matched by the closure passed to filter, or — if no filter is set — by a case-insensitive substring match on the item_label.

The search input becomes a child of the dropdown panel only, not of the trigger: the closed combo box looks identical whether searchable or not.

The query signal is created internally. Use search_query to supply your own if you want to observe or drive the query externally.

pub fn search_query(mut self, query: Signal<String>) -> Self

Bind the search field to an external Signal<String>. Implies searchable(true). Useful for observing or programmatically setting the query from outside the widget (e.g. a "Clear" button, persistence across sessions).

pub fn filter(mut self, f: impl Fn(&str, &T) -> bool + 'static) -> Self

Custom match predicate for searchable mode. Called on every visible-item pass with the current query string (as typed, not normalized) and a reference to the item; return true to keep the item in the filtered list. Only consulted when searchable is true. Ignored otherwise.