Model picker
AIProChoose the model that runs a feature. Each option shows a provider glyph, a short descriptor, capabilities, context window and relative speed and cost.
npx prodular-ui add model-pickerPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
In the composer toolbar
The compact trigger is a ghost button that fits next to the other tools, with the context window ring beside send.
"use client"
import * as React from "react"import { PaperclipIcon } from "lucide-react"
import { ModelPicker, type ModelOption } from "@/components/ui/model-picker"import { PromptInput, PromptInputActions, PromptInputButton, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar, PromptInputTools,} from "@/components/ui/prompt-input"import { ContextWindow } from "@/components/ui/usage-meter"
const models: ModelOption[] = [ { id: "atlas-2", name: "Atlas 2", provider: "Northlight", description: "Balanced for everyday finance and support work", capabilities: ["vision", "tools", "files"], contextWindow: 200_000, speed: 2, cost: 2, group: "Recommended", tag: "Default", }, { id: "atlas-2-mini", name: "Atlas 2 Mini", provider: "Northlight", description: "Quick answers, triage and classification", capabilities: ["tools"], contextWindow: 128_000, speed: 3, cost: 1, group: "Recommended", }, { id: "cinder-reason", name: "Cinder Reason 1", provider: "Cinder", description: "Thinks longer for reconciliations and audits", capabilities: ["reasoning", "tools", "files"], contextWindow: 400_000, speed: 1, cost: 3, group: "All models", tag: "New", }, { id: "halden-swift", name: "Halden Swift", provider: "Halden", description: "Low latency drafting and summaries", capabilities: ["vision", "web"], contextWindow: 64_000, speed: 3, cost: 1, group: "All models", },]
export default function ModelPickerComposer() { const [model, setModel] = React.useState("atlas-2") const max = models.find((m) => m.id === model)?.contextWindow ?? 200_000
return ( <div className="w-full max-w-xl"> <PromptInput onSubmit={() => {}}> <PromptInputTextarea placeholder="Summarize open disputes over $5,000" /> <PromptInputToolbar> <PromptInputTools> <PromptInputButton tooltip="Attach files"> <PaperclipIcon /> </PromptInputButton> <ModelPicker models={models} value={model} onValueChange={setModel} /> </PromptInputTools> <PromptInputActions> <ContextWindow used={52_300} max={max} /> <PromptInputSubmit /> </PromptInputActions> </PromptInputToolbar> </PromptInput> </div> )}In a settings form
The full trigger shows the descriptor so people can compare without opening the list. Models outside the plan stay visible but disabled.
Runs on every new ticket. Pick a fast model.
Runs once a month on the full ledger.
"use client"
import * as React from "react"
import { ModelPicker, type ModelOption } from "@/components/ui/model-picker"
const models: ModelOption[] = [ { id: "atlas-2", name: "Atlas 2", provider: "Northlight", description: "Balanced for everyday finance and support work", capabilities: ["vision", "tools", "files"], contextWindow: 200_000, speed: 2, cost: 2, tag: "Default", }, { id: "atlas-2-mini", name: "Atlas 2 Mini", provider: "Northlight", description: "Quick answers, triage and classification", capabilities: ["tools"], contextWindow: 128_000, speed: 3, cost: 1, }, { id: "cinder-reason", name: "Cinder Reason 1", provider: "Cinder", description: "Thinks longer for reconciliations and audits", capabilities: ["reasoning", "tools", "files"], contextWindow: 400_000, speed: 1, cost: 3, tag: "New", }, { id: "halden-swift", name: "Halden Swift", provider: "Halden", description: "Low latency drafting and summaries", capabilities: ["vision", "web"], contextWindow: 64_000, speed: 3, cost: 1, disabled: true, tag: "Not on your plan", },]
export default function ModelPickerSettings() { const [triage, setTriage] = React.useState("atlas-2-mini") const [audit, setAudit] = React.useState("cinder-reason")
return ( <div className="flex w-full max-w-md flex-col gap-5"> <div className="flex flex-col gap-1.5"> <label htmlFor="triage-model" className="text-sm font-medium"> Ticket triage </label> <ModelPicker id="triage-model" aria-label="Ticket triage model" variant="full" models={models} value={triage} onValueChange={setTriage} /> <p className="text-[13px] text-muted-foreground">Runs on every new ticket. Pick a fast model.</p> </div> <div className="flex flex-col gap-1.5"> <label htmlFor="audit-model" className="text-sm font-medium"> Month end reconciliation </label> <ModelPicker id="audit-model" aria-label="Reconciliation model" variant="full" models={models} value={audit} onValueChange={setAudit} /> <p className="text-[13px] text-muted-foreground">Runs once a month on the full ledger.</p> </div> </div> )}Anatomy
<ModelPicker models={models} value={model} onValueChange={setModel} variant="compact"/>Guidance
Use it when
- Use the compact variant in a composer toolbar and the full variant in settings and forms.
- Write descriptions as what the model is good for in your product, not marketing copy: Thinks longer for reconciliations and audits.
- Group options with group when there are more than four, for example Recommended and All models.
- Show models that are not on the person's plan as disabled with a tag, so they know they exist.
Avoid
- Real provider logos. Use the monochrome letter glyph or pass a neutral icon in glyph.
- Raw model ids as names. Show the human name and keep the id in value.
- Listing more than eight models. Curate the list for the task.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Render ModelPicker controlled with value and onValueChange. Store and send the model id, never the display name.
- Pass contextWindow in tokens, speed and cost as relative 1 to 3 values, and capabilities from vision, tools, reasoning, files, web. Leave a field out rather than guessing it.
- In a composer use variant="compact" inside PromptInputTools. In a form use variant="full" with a visible label linked through id, or pass aria-label.
- When the selected model changes, update anything that depends on it, such as ContextWindow max.
- Do not use brand logos or brand colors for providers. Use ModelGlyph.
API
ModelPicker
| Prop | Type | Default | Description |
|---|---|---|---|
| models | ModelOption[] | id, name, provider, description, capabilities, contextWindow, speed, cost, group, tag, glyph, disabled. | |
| value / onValueChange | string / (id: string) => void | The selected model id. | |
| variant | "compact" | "full" | "compact" | compact for toolbars, full for forms. |
| aria-label | string | "Model" | Accessible name for the trigger. |
ModelGlyph
| Prop | Type | Default | Description |
|---|---|---|---|
| provider | string | Its first letter becomes the mark. |
ModelRating
| Prop | Type | Default | Description |
|---|---|---|---|
| kind / value | "speed" | "cost" / 1 | 2 | 3 | Three pips with an accessible word. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add model-picker# or, with the registry in components.jsonnpx shadcn@latest add @prodular/model-picker