Morph-to select
Introduction
Section titled “Introduction”A morph-to field is two questions: what kind of record? and which one? MorphToSelect renders a type <select> next to a record <select>. Changing the type clears the chosen id and asks the server for that type’s options; typing in the search box filters those options.
State is a dict {type, id} (or a "type:id" string). .type_attribute() / .id_attribute() rename the keys when your columns are not type / id.
For large lists, skip static options and give Orbit a loader:
MorphToSelect.make("commentable") .searchable() .types([{"type": "post", "label": "Post"}, {"type": "video", "label": "Video"}]) .options_using(lambda type="", search="", **_: load_commentables(type, search))The create/edit host calls that loader as callback(type=..., search=...) whenever the type changes or the operator types.
Each variation below includes the fluent API and light/dark screenshots of the rendered control.
Basic morph-to select
Section titled “Basic morph-to select”Provide typed option maps so the id select can show records for the chosen morph type.
MorphToSelect.make('assignee') .label('Assignee') .types([ { 'type': 'user', 'label': 'User', 'options': {'1': 'Ada Lovelace', '2': 'Alan Turing'}, }, { 'type': 'team', 'label': 'Team', 'options': {'10': 'Platform', '11': 'Design'}, }, ])

Searchable wrapper
Section titled “Searchable wrapper”.searchable() adds data-searchable on the wrapper for host search behavior across the morph selects.
MorphToSelect.make('owner') .label('Owner') .searchable() .types([ {'type': 'user', 'label': 'User', 'options': {'1': 'Ada'}}, {'type': 'org', 'label': 'Organization', 'options': {'5': 'Acme'}}, ])

Custom type and id attributes
Section titled “Custom type and id attributes”When your polymorphic columns are not type / id, remap with .type_attribute() and .id_attribute() so dict state round-trips correctly.
MorphToSelect.make('notable') .label('Notable') .type_attribute('notable_type') .id_attribute('notable_id') .types(['App\\Models\\Post', 'App\\Models\\Video'])

Class-string types
Section titled “Class-string types”Passing bare class strings builds type options from the final segment of each name; populate id options separately via .options() or per-type maps.
MorphToSelect.make('subject') .label('Subject') .types([ 'App\\Models\\User', 'App\\Models\\Team', ]) .options({'1': 'Ada', '2': 'Grace'})

Live search
Section titled “Live search”.searchable() plus .options_using(...) is the production path. The host keeps morph_search per field, re-renders the id select with the filtered map, and clears the search when the type changes.
def load_owners(type: str = "", search: str = "", **_: object) -> dict[str, str]: rows = users() if type == "user" else teams() needle = search.casefold() return { str(row["id"]): row["name"] for row in rows if not needle or needle in row["name"].casefold() }
MorphToSelect.make("owner") .label("Owner") .searchable() .types([{"type": "user", "label": "User"}, {"type": "team", "label": "Team"}]) .options_using(load_owners)

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