import { ModelPicker } from "@elabs-ai/components-ui";npm install @elabs-ai/components-tokens @elabs-ai/components-uiPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui ModelPicker component (from @elabs-ai/components-ui) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look ModelPicker up before writing code: `npx -y @elabs-ai/components-cli docs ModelPicker` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-ui is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Examples
Loading
Skeleton rows while the first load is in flight. The `play` opens the popover — issue #121: axe never rendered `CommandList` (cmdk's `role="listbox"`) until a story actually opened it, which is how a structural `aria-required-children` violation shipped unnoticed.
Stale With Inline Error
A failed refresh over usable stale data: an inline strip ABOVE a list that still works. Collapsing this into the error panel would throw away a list the user could still pick from.
Error State
Nothing to show AND the load failed — the panel takes over, with a retry.
Empty
Nothing to show, but nothing went wrong — a different message, deliberately.
Empty With Retry
Nothing to show, nothing went wrong, AND a retry is offered — the empty panel's `Retry` button, the specific case where the button previously landed inside `role="listbox"` (issue #121).
List No Search Results
The everyday path issue #121 was really about: a fully-populated, working list where the search query happens to match nothing. `CommandEmpty` renders while the list itself stays mounted with zero options — the case that shipped broken because no story ever typed a non-matching query.
Disabled
Controlled
Programmatic open via `open`/`onOpenChange` — e.g. re-opening the picker when a previously pinned target becomes unavailable, without the consumer having to reach into internal state.
API
ModelPicker
| Prop | Type | Description |
|---|---|---|
groupsrequired | readonly ModelPickerGroup[] | Grouped rows. Grouping and heading text are the caller's, never derived. |
value | string | The currently pinned row's id. |
onSelect | (item: ModelPickerItem) => void | Fires with the chosen row; the popover closes first. |
status | ModelPickerStatus | Drives the four bodies — see `modelPickerBody`. Default `"ready"`. |
triggerLabelrequired | ReactNode | Trigger text — usually the pinned row's label. |
onRetry | () => void | Retry action for the error strip and the empty panel. Omit to hide it. |
searchPlaceholder | string | Search field placeholder. |
disabled | boolean | |
contentClassName | string | Extra classes for the popover panel (default width `w-96`). |
open | boolean | Controlled popover open state. Omit for the uncontrolled default (internal `useState(false)`, unchanged). `isControlled = open !== undefined` and the mode never flips mid-life — see `component-api.md` "Controlled/uncontrolled". |
onOpenChange | (open: boolean) => void | Fires whenever the popover would open/close — a trigger click, or a row select. |
footer | ReactNode | Rendered inside the popover BELOW the list — a persistent action row that is not one of the options (a free-text entry, a "browse…" affordance). Outside `CommandList` on purpose: it must not be filtered by the search query and must not participate in cmdk's roving selection. |