Model picker
Free · MITModel dropdown grouped by provider with icon slots, speed and quality meters that grow in, context window and price per million tokens, a gliding highlight and full keyboard support with type-ahead.
Installation
$ npx shadcn@latest add https://goodui.dev/r/model-picker.jsonThe file lands in components/goodui/model-picker.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/model-picker works too.
Usage
import { ModelPicker, type ModelOption } from "@/components/goodui/model-picker"
const MODELS: ModelOption[] = [
{ id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", provider: "Anthropic", speed: 4, quality: 5, context: 200_000, price: { input: 3, output: 15 }, badge: "Recommended" },
{ id: "gpt-5-mini", name: "GPT-5 mini", provider: "OpenAI", speed: 5, quality: 3, context: 400_000, price: { input: 0.25, output: 2 } },
]
<ModelPicker models={MODELS} value={model} onValueChange={setModel} />When to use
- Letting people pick the model for a chat, an agent or a batch job, with enough information to choose (speed, quality, context, price).
- Toolbars and composers: the ghost, small variant fits next to other controls.
- Multi-provider products where rows should be grouped by provider.
- A generic single-select field without model metadata: use select.
- Thousands of options with search: use combobox.
- Comparing models in depth: a table; the picker shows one line of facts per model.
| If you need | Use |
|---|---|
| Field in a settings form | variant="outline" size="md" |
| Inside a composer or toolbar | variant="ghost" size="sm" showMeters={false} |
| Flat list, no provider headings | groupByProvider={false} |
| Real logos | providerIcons={{ OpenAI: <Logo /> }} or icon per model |
| Retired model still listed | disabled on the model |
Examples
Compact trigger aligned to the end
<ModelPicker models={MODELS} variant="ghost" size="sm" align="end" showMeters={false} />German prices and labels
<ModelPicker models={MODELS} locale="de-DE" labels={{ chooseModel: "Modell wählen", speed: "Tempo", quality: "Qualität" }} />API reference
| Prop | Type | Default |
|---|---|---|
| models* | ModelOption[] | — |
| value Selected model id (controlled). | string | — |
| defaultValue | string | — |
| onValueChange | (id: string, model: ModelOption) => void | — |
| providerIcons Icon per provider name, e.g. { OpenAI: <OpenAiLogo />, Anthropic: <AnthropicLogo /> }. Missing ones get a lettered tile. | Record<string, React.ReactNode> | — |
| variant Trigger look: a bordered field or a quiet ghost button for toolbars. | "outline" | "ghost" | "outline" |
| size | "sm" | "md" | "md" |
| groupByProvider Group rows under provider headings. | boolean | true |
| showMeters | boolean | true |
| showContext | boolean | true |
| showPrice | boolean | true |
| align Which trigger edge the menu aligns to. | "start" | "end" | "start" |
| locale Locale for the context window and prices. | string | "en-US" |
| placeholder | string | — |
| disabled | boolean | — |
| labels Text for built-in controls and screen readers; pass translations here. | Partial<ModelPickerLabels> | — |
Also accepts every <div> attribute.
Accessibility
| Enter / Space / ArrowDown / ArrowUp | On the trigger: opens the list and focuses it. |
| ArrowDown / ArrowUp | Moves the active row, skipping disabled models, and wraps. |
| Home / End | First or last enabled model. |
| Letters | Type-ahead: jumps to the first model whose name starts with what you typed (600 ms window). |
| Enter / Space | Selects the active model and returns focus to the trigger. |
| Escape | Closes and returns focus to the trigger. Tab closes too. |
- The trigger has aria-haspopup="listbox", aria-expanded, aria-controls and an aria-label that includes the current model name.
- The list is role="listbox" with aria-activedescendant; rows are role="option" with aria-selected and aria-disabled.
- Meters are announced as text ("Speed 4 of 5"); the bars themselves are decorative. The selected row has a visually hidden "Selected".
- Built-in strings go through the labels prop for translation.
Motion
- The list springs open (stiffness 520, damping 34) from the trigger's edge and flips above it when fewer than 380px remain below.
- Rows fade in staggered by 25 ms (up to 8 rows); the highlight glides between rows as a shared-layout spring; meter bars grow from the baseline with a 40 ms stagger when the list opens.
- Reduced motion: the list fades, rows do not stagger, the highlight jumps and the bars appear instantly.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- Prices are USD per million tokens; context is in tokens and formatted compactly (200K, 1M).
- onValueChange receives (id, model). Pass value to control it; otherwise it starts from defaultValue or the first model.
- Provider icons: providerIcons keyed by the provider string, or icon on a model. Without either, a lettered tile colored from the provider name is used.
- The list is rendered in place (absolute), not in a portal; a parent with overflow:hidden will clip it.
FAQ
Can I show only some facts?
Yes: showMeters, showContext and showPrice each hide their column.
How do I add a price unit other than per 1M?
Set labels.perMillion; it is the tooltip on the price. The numbers themselves are whatever you pass.
Related
Prompt input with an auto-growing textarea, attachment chips from picker, paste or drop, a slash-command menu, model and mode selectors, a token ring against the context limit, Enter or Cmd+Enter to send and a Stop button while streaming.
Fully custom single or multi select: outline, filled, ghost, underline, pill and neo triggers; icons, avatars, descriptions and groups; search with match highlight, chips, clear, type-ahead and full keyboard; animated menu that flips up when space runs out.
Autocomplete combobox (ARIA 1.2) with outline, filled, ghost, underline, pill and neo fields, tones, sizes and shadows; local ranked matching or debounced async search with abort, cache, loading, empty and error-with-retry states, every matched word highlighted, groups, avatars, single or multi-select chips, an “Add …” create row and automatic virtualization for 10,000+ rows, in a portaled list that flips above the field.
Menu button and right-click context menu with default, glass, neo, inverted and flat surfaces, soft, solid, bar or outline highlights, tones, sizes, shapes and shadows; icons, descriptions, badges and live keyboard shortcuts, checkbox items, radio groups, labelled groups, destructive items and nested sub-menus with hover intent and a safe triangle; scale, fade, slide or stagger entrances, type-ahead and full APG keyboard, portaled and flipped to stay on screen.