Morph select
A select whose trigger grows into the list, with a gliding highlight and type-ahead.
$ pnpm dlx shadcn@4.23.0 add @xzero-ui/morph-select- Type: Component (inputs)
- Access: Free, open source
- Page: https://ui.xzero.co.uk/components/morph-select
- Markdown: https://ui.xzero.co.uk/components/morph-select/markdown
- Registry item: https://mcp.xzero.co.uk/r/morph-select.json
- Source file:
registry/components/morph-select/morph-select.tsx - Dependencies: motion, lucide-react
- Keywords: inputs, new, react select, animated select, morphing dropdown, listbox, select with search, grouped select, custom select component, type-ahead select
When to use
- Form fields and property panels where one value is picked from a known list.
- Grouped lists like time zones, assignees, or projects, with optional icons and meta.
- Compact toolbars where the picker should look like a pill until opened.
When not to use
- Use combobox when users type free text or the list is very long and remote.
- Use multi-select when more than one value can be chosen.
- Use dropdown-menu for actions rather than a stored value.
Installation
Register both Xzero UI namespaces in your project's components.json:
{
"registries": {
"@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
"@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
}
}
npx shadcn@4.23.0 add @xzero-ui/morph-select
Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.
Usage
import { useState } from "react";
import { MorphSelect } from "@/components/xzero/morph-select/morph-select";
export function TimezoneField() {
const [zone, setZone] = useState<string | null>("europe/zurich");
return (
<MorphSelect
label="Time zone"
name="timezone"
value={zone}
onValueChange={setZone}
items={[
{ label: "Europe", options: [
{ value: "europe/london", label: "London", meta: "UTC+0" },
{ value: "europe/zurich", label: "Zurich", meta: "UTC+1", keywords: "switzerland" },
] },
{ label: "Americas", options: [
{ value: "america/new_york", label: "New York", meta: "UTC−5" },
{ value: "america/los_angeles", label: "Los Angeles", meta: "UTC−8" },
] },
]}
/>
);
}
API reference
MorphSelect
A select whose trigger grows into the option list, with groups, type-ahead, optional search, and a label that flies back into the trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
label (required) | string | – | Visible label, also the accessible name of the trigger and the list. |
hideLabel | boolean | false | Keeps the label for assistive technology only. |
items (required) | MorphSelectItem[] | – | Options { value, label, icon?, meta?, keywords?, disabled? } or groups { label, options }, in display order. |
value | string | null | – | Controlled selected value. |
defaultValue | string | null | null | Uncontrolled starting value. |
onValueChange | (value: string, option: MorphSelectOption) => void | – | Called with the new value and its option. |
placeholder | string | "Select" | Trigger text with no selection. |
searchable | boolean | "auto" | "auto" | Shows a search field in place of the trigger when open. auto turns it on above eight options. |
searchPlaceholder | string | – | Search field placeholder. Defaults to the selected label, then "Search". |
panelWidth | number | 272 | Width of the open surface in px. Never narrower than the trigger. |
maxListHeight | number | 296 | Tallest the option list gets before it scrolls, in px. |
align | "start" | "end" | "start" | Which edge of the trigger stays put while the surface grows. |
name | string | – | Adds a hidden input for native form submission. |
disabled | boolean | false | Disables the trigger. |
className | string | – | Extra class on the root. |
Keyboard interactions
| Keys | Action |
|---|---|
| Enter / Space / ArrowDown / ArrowUp | Opens the list on the selected option; Enter or Space picks the active option when open. |
| ArrowDown / ArrowUp | Moves the active option, skipping disabled ones. |
| PageDown / PageUp | Moves eight options at a time. |
| Home / End | Moves to the first or last option; when closed, opens on it. |
| Alt + ArrowUp | Picks the active option and closes. |
| Printable characters | Type-ahead jumps to a matching label, or starts a search when search is on. |
| Escape | Closes and returns focus to the trigger. |
| Tab | Closes and moves focus on without picking. |
Accessibility
- The trigger is a role="combobox" with aria-haspopup="listbox", aria-expanded, and aria-activedescendant pointing at the active option.
- With search on, the input takes over as the combobox with aria-autocomplete="list", and the trigger turns inert.
- Options are role="option" with aria-selected and aria-disabled; groups are role="group" labelled by their heading.
- A polite status announces the number of search results or No matches.
Motion
- The trigger surface grows into the list on a physical spring with slight bounce and folds back without overshoot; the corner radius morphs with it.
- One highlight glides between options; the chosen label lifts out of the list and flies into the trigger while the trigger width springs.
- Values in the trigger roll up or down depending on whether the new option is later or earlier in the list.
- Reduced motion jumps the size, removes the fly and roll, and uses short fades.
Responsive behavior
- The closed trigger sizes to its label up to min(20rem, 100vw - 2rem); the open list is max(trigger width, min(panelWidth, 100vw - 2rem)).
- Set align="end" for triggers near the right edge so the surface grows leftward.
- Hover highlight follows the mouse only; on touch a tap picks directly, and hover styles apply only on fine pointers.
Performance
- A hidden measuring copy and a ResizeObserver size the trigger and list; the surface animates width, height, and radius through motion values.
- All options render; there is no virtualization, so keep lists to a few hundred items.
- Search filters in memory on label, meta, and keywords.
Notes for AI
- Use for compact property pickers and form fields with up to a few hundred options, including avatars or icons.
- Add keywords to options so search matches synonyms; meta shows trailing detail like counts or offsets.
- Pass name to submit with a native form; the hidden input carries the value.
- Use combobox when people mostly type free text, and multi-select for several values.
Related
- Select: A compact choice field with a keyboard friendly menu.
- Combobox: Search and select from a list without leaving the field.
- Multi-select: Select several values while keeping the field readable.
- Dropdown menu: A focused list of actions anchored to a trigger.
Also in selects
- Chip group: Filter by a few facets with chips that morph as you pick them.
Guidance for AI tools
Morph select: A select whose trigger grows into the list, with a gliding highlight and type-ahead. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.
Full library index: https://ui.xzero.co.uk/llms.txt