82 / 122 · Composer
Model picker
The full list rather than the rail: grouped by family, priced, with what each one can do.
Anthropic
Local
Installation
npx shadcn@latest add "@assistant-ui/elements-model-picker"
Usage
import { ModelPicker } from "@/components/elements/model-picker";
<ModelPicker models={models} selectedId={selectedId} onSelect={setSelectedId} />Props
ModelPicker
models*PickableModel[]Every model on offer. Families are derived from the entries and keep their first-seen order.
selectedId*stringWhich model is active.
onSelect(id: string) => voidCalled when a model is picked.
classNamestringExtra classes merged onto the root.
PickableModel
id*stringStable identity, compared against selectedId and reported by onSelect.
name*stringDisplay name of the model.
family*stringGroup heading the model sits under.
context*stringWindow size, pre-formatted.
price*stringCost, pre-formatted. The element never does currency math.
capabilities*string[]Short capability chips: vision, tools, thinking.
Source
model-picker.tsx"use client";
import { CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { field, mono, paper } from "./surfaces";
export interface PickableModel {
id: string;
name: string;
family: string;
context: string;
price: string;
capabilities: readonly string[];
}
export function ModelPicker({
models,
selectedId,
onSelect,
className,
}: {
models: readonly PickableModel[];
selectedId: string;
onSelect?: (id: string) => void;
className?: string;
}) {
const families = [...new Set(models.map((model) => model.family))];
return (
<div
className={cn(
paper,
"flex w-full max-w-sm flex-col gap-1 rounded-2xl p-2",
className,
)}
>
{families.map((family) => (
<div key={family} className="flex flex-col">
<span className={cn(mono, "text-foreground/30 px-2 pt-2 pb-1")}>
{family}
</span>
{models
.filter((model) => model.family === family)
.map((model) => {
const selected = model.id === selectedId;
return (
<button
key={model.id}
type="button"
aria-pressed={selected}
onClick={() => onSelect?.(model.id)}
className={cn(
"flex items-center gap-2.5 rounded-xl px-2 py-2 text-start transition-colors",
selected
? "bg-foreground/[0.06]"
: "hover:bg-foreground/[0.035]",
)}
>
<span className="flex size-3.5 shrink-0 items-center justify-center">
{selected && (
<CheckIcon className="fade-in zoom-in-90 animate-in text-foreground/70 size-3.5 duration-200" />
)}
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span className="truncate text-[13.5px]">{model.name}</span>
<span className="flex flex-wrap gap-1">
{model.capabilities.map((capability) => (
<span
key={capability}
className={cn(
field,
mono,
"text-foreground/45 rounded px-1 py-px",
)}
>
{capability}
</span>
))}
</span>
</span>
<span className="flex shrink-0 flex-col items-end gap-1">
<span
className={cn(mono, "text-foreground/35 tabular-nums")}
>
{model.context}
</span>
<span
className={cn(mono, "text-foreground/25 tabular-nums")}
>
{model.price}
</span>
</span>
</button>
);
})}
</div>
))}
</div>
);
}