Skip to content

Select

Select is Orbit’s control for choosing one value (or many, when multiple is enabled) from a known set. You can feed it a static value → label map, nested option groups, a Python Enum, or options loaded from a related model. Advanced modes add client-side or AJAX search, create/edit modals for related records, and limits on how many options appear. Pair Select with Multi select when the field always stores a list, or call .multiple() on Select itself.

Each variation below includes a short explanation, the fluent API to paste into your schema, and light/dark screenshots of the rendered control.

The default control is a native HTML <select> bound to form state. Pass a flat dict of value → label to .options(). Labels dehydrate as the stored keys, not the display text.

app/orbit/resources/post_resource.py
Select.make('status')
.label('Status')
.options({
'draft': 'Draft',
'reviewing': 'Reviewing',
'published': 'Published',
})

Orbit Basic select (light)

Orbit Basic select (dark)

.native(True) (the default) keeps the browser <select> for short, simple lists. Orbit switches to its combobox UI when you call .searchable(), .multiple(), .allow_html(), or .native(False).

The combobox is one control: trigger, optional search field, listbox dropdown, keyboard navigation (↑/↓/Enter/Esc), a clear button, and chips for multi-select. A visually hidden <select> stays wired to Conduit/wire:model so dehydration is unchanged.

app/orbit/resources/post_resource.py
Select.make('status')
.label('Status')
.options({
'draft': 'Draft',
'reviewing': 'Reviewing',
'published': 'Published',
})
.native(False)

Orbit Native vs custom select (light)

Orbit Native vs custom select (dark)

.searchable() enables the combobox search field. Static options filter client-side as you type. Relationship selects without .preload() set data-ajax-search and call Conduit searchSelectOptions, so the host re-renders matching rows (default limit 50).

app/orbit/resources/post_resource.py
Select.make('author_id')
.label('Author')
.options({
1: 'Ada Lovelace',
2: 'Grace Hopper',
3: 'Katherine Johnson',
})
.searchable()

Orbit Searching options (light)

Orbit Searching options (dark)

When options come from a database or external API, skip a static .options() map and supply .get_search_results_using(). Return a value → label dict for the current search string. Pair with .get_option_label_using() so the currently selected value still has a label before the user searches.

app/orbit/resources/post_resource.py
Select.make('author_id')
.label('Author')
.searchable()
.get_search_results_using(
lambda search, **_: {
a.id: a.name
for a in Author.query().where('name', 'like', f'%{search}%').limit(50)
}
)
.get_option_label_using(
lambda value, **_: Author.find(value).name if value else None
)

Orbit Custom search results (light)

Orbit Custom search results (dark)

Customize the empty-search placeholder and async feedback strings. .search_prompt() sets the hint before typing; .loading_message(), .searching_message(), and .no_search_results_message() cover loading and empty states.

app/orbit/resources/post_resource.py
Select.make('author_id')
.label('Author')
.relationship('author', 'name')
.searchable()
.search_prompt('Search authors by name')
.loading_message('Loading authors…')
.searching_message('Searching authors…')
.no_search_results_message('No authors found.')

Orbit Search prompt and messages (light)

Orbit Search prompt and messages (dark)

Nest a dict of groups under .options(): outer keys become group headings, inner dicts are the selectable values. Groups work with searchable and native selects alike.

app/orbit/resources/post_resource.py
Select.make('status')
.label('Status')
.searchable()
.options({
'In process': {
'draft': 'Draft',
'reviewing': 'Reviewing',
},
'Reviewed': {
'published': 'Published',
'rejected': 'Rejected',
},
})

Orbit Grouping options (light)

Orbit Grouping options (dark)

.enum() builds the options map from a Python Enum (member value → humanized name). Useful when the field stores enum values already used elsewhere in the app.

app/orbit/resources/post_resource.py
from enum import Enum
class Status(str, Enum):
DRAFT = 'draft'
PUBLISHED = 'published'
Select.make('status')
.label('Status')
.enum(Status)

Orbit Enum options (light)

Orbit Enum options (dark)

.multiple() stores a list of selected keys instead of a single value. Prefer MultiSelect when the field is always multi; both share the same Select APIs (search, relationships, limits, reorder).

app/orbit/resources/post_resource.py
Select.make('technologies')
.label('Technologies')
.multiple()
.options({
'tailwind': 'Tailwind CSS',
'alpine': 'Alpine.js',
'laravel': 'Laravel',
})

Orbit Multiple selection (light)

Orbit Multiple selection (dark)

.relationship() loads options from a related model on the record (for example an album’s artist, or many tags). Pass the relationship name and title attribute, or option_label='{name} - {country}' for multi-column labels. The dehydrated value is typically the related record’s primary key (or a list of keys when .multiple() is on). Without .preload(), searchable relationship selects fetch pages of .options_limit() results (default 50) as the user types.

app/orbit/resources/album_resource.py
Select.make('artist_id')
.label('Artist')
.relationship('artist', option_label='{name} - {country}')
.searchable()

Orbit Relationship selects (light)

Orbit Relationship selects (dark)

.preload() eagerly loads a capped option set on first render instead of waiting for search. Combine with .searchable() so users can still filter the preloaded list.

app/orbit/resources/album_resource.py
Select.make('artist_id')
.label('Artist')
.relationship('artist', 'name')
.searchable()
.preload()

Orbit Preloading relationship options (light)

Orbit Preloading relationship options (dark)

.options_limit() caps how many relationship or AJAX results are returned per request (default 50). Raise it for denser catalogs; keep it modest to protect query cost.

app/orbit/resources/album_resource.py
Select.make('artist_id')
.label('Artist')
.relationship('artist', 'name')
.searchable()
.options_limit(100)

Orbit Options limit (light)

Orbit Options limit (dark)

.boolean() replaces custom options with Yes / No (1 / 0). Ideal for quick affirmative fields that still need a select rather than a checkbox.

app/orbit/resources/post_resource.py
Select.make('featured')
.label('Featured on homepage?')
.boolean()

Orbit Boolean options (light)

Orbit Boolean options (dark)

.disable_option_when() receives each option value and returns True when that option should be non-selectable — retired statuses, sold-out SKUs, and similar.

app/orbit/resources/post_resource.py
Select.make('status')
.label('Status')
.options({
'draft': 'Draft',
'published': 'Published',
'archived': 'Archived',
})
.disable_option_when(lambda value, **_: value == 'archived')

Orbit Disabling specific options (light)

Orbit Disabling specific options (dark)

Long labels can truncate awkwardly in the dropdown. .wrap() allows option text to wrap onto multiple lines inside the custom select.

app/orbit/resources/post_resource.py
Select.make('policy')
.label('Retention policy')
.native(False)
.options({
'30d': 'Delete drafts older than 30 days after last edit',
'90d': 'Archive published posts after 90 days of inactivity',
})
.wrap()

Orbit Wrapping option labels (light)

Orbit Wrapping option labels (dark)

By default option labels are escaped. .allow_html() renders trusted HTML in labels (badges, emphasis). Only enable this for content you control — untrusted strings are an XSS risk.

app/orbit/resources/post_resource.py
Select.make('priority')
.label('Priority')
.native(False)
.options({
'high': '<span class="text-danger">High</span>',
'low': '<span class="text-muted">Low</span>',
})
.allow_html()

Orbit Allowing HTML in labels (light)

Orbit Allowing HTML in labels (dark)

.create_option_form() attaches a modal schema so users can insert a related record without leaving the form. .create_option_using() customizes persistence and must return the new option’s primary key.

app/orbit/resources/album_resource.py
Select.make('artist_id')
.label('Artist')
.relationship('artist', 'name')
.searchable()
.create_option_form([
TextInput.make('name').required(),
TextInput.make('country'),
])
.create_option_using(lambda data, **_: Artist.create(**data).id)

Orbit Creating new options (light)

Orbit Creating new options (dark)

.edit_option_action() exposes an action beside the select so users can open the currently selected related record for editing without leaving the form. Pass True to enable the default action, or a named action string when you wire a custom handler.

app/orbit/resources/album_resource.py
Select.make('artist_id')
.label('Artist')
.relationship('artist', 'name')
.searchable()
.edit_option_action()

Orbit Editing the selected option (light)

Orbit Editing the selected option (dark)

When .selectable_placeholder(True) (default), the empty placeholder row can be chosen to clear the value. Disable it when a blank selection should not be allowed after the user picks a real option.

app/orbit/resources/post_resource.py
Select.make('status')
.label('Status')
.placeholder('Choose a status')
.options({
'draft': 'Draft',
'published': 'Published',
})
.selectable_placeholder(False)

Orbit Selectable placeholder (light)

Orbit Selectable placeholder (dark)

On multi selects, .min_items() and .max_items() constrain how many options may be chosen. Validation fails when the selection falls outside the range.

app/orbit/resources/post_resource.py
Select.make('tags')
.label('Tags')
.multiple()
.options({
'orbit': 'Orbit',
'forms': 'Forms',
'tables': 'Tables',
})
.min_items(1)
.max_items(3)

Orbit Limiting selection count (light)

Orbit Limiting selection count (dark)

.reorderable() lets users drag selected chips into a meaningful order when sequence matters (priority lists, display order). Requires .multiple().

app/orbit/resources/post_resource.py
Select.make('tags')
.label('Tags')
.multiple()
.reorderable()
.options({
'orbit': 'Orbit',
'forms': 'Forms',
'tables': 'Tables',
})

Orbit Reordering selected options (light)

Orbit Reordering selected options (dark)

Closures work on .label(), .helper_text(), .placeholder(), .visible(), .disabled(), and .required() where applicable — see Form closures.