ModelSelector
Dropdown for selecting an AI model. Shows the active model and provider badge; opens an animated popover listing models with colored dots and capability descriptions.
Use and inspectUse this as a presentational interface around your own submit, stream, or state handlers. Reset preview only remounts the local example; it does not send a request or connect to a model.Next: inspect the source and wire its callback to a bounded backend action.
ModelSelectorDropdown for selecting an AI model. Shows the active model and provider badge; opens an animated popover listing models with colored dots and capability descriptions.
"use client";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
export type ModelOption = {
id: string;
name: string;
provider: string;
color: string;
description: string;
};
const DEFAULT_MODELS: ModelOption[] = [
{ id: "claude-opus-4-5", name: "claude-opus-4-5", provider: "Anthropic", color: "#f093fb", description: "Most capable, complex reasoning" },
{ id: "claude-sonnet-4-6", name: "claude-sonnet-4-6", provider: "Anthropic", color: "#667eea", description: "Balanced speed and intelligence" },
{ id: "claude-haiku-4-5", name: "claude-haiku-4-5", provider: "Anthropic", color: "#4ade80", description: "Fast, lightweight tasks" },
{ id: "gpt-4o", name: "gpt-4o", provider: "OpenAI", color: "#34d399", description: "Multimodal reasoning" },
{ id: "gemini-2.0-flash", name: "gemini-2.0-flash", provider: "Google", color: "#60a5fa", description: "Speed-optimized generation" },
];
type ModelSelectorProps = {
value: string;
onChange: (model: string) => void;
models?: ModelOption[];
};
export function ModelSelector({
value,
onChange,
models = DEFAULT_MODELS,
}: ModelSelectorProps) {
const [open, setOpen] = useState(false);
const selected = models.find((m) => m.id === value) ?? models[0];
return (
<div className="pk-model-sel">
<button
className="pk-model-sel-trigger"
onClick={() => setOpen(!open)}
aria-haspopup="listbox"
aria-expanded={open}
>
<span className="pk-model-sel-dot" style={{ backgroundColor: selected.color }} />
<span className="pk-model-sel-name">{selected.name}</span>
<span className="pk-model-sel-provider">{selected.provider}</span>
<span className="pk-model-sel-arrow">{open ? "▴" : "▾"}</span>
</button>
<AnimatePresence>
{open && (
<motion.div
className="pk-model-sel-dropdown"
initial={{ opacity: 0, y: -8, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: -8, scale: 0.97 }}
transition={{ duration: 0.15, ease: "easeOut" }}
role="listbox"
>
{models.map((m) => (
<button
key={m.id}
className={"pk-model-sel-item" + (m.id === value ? " pk-model-sel-item--active" : "")}
onClick={() => { onChange(m.id); setOpen(false); }}
role="option"
aria-selected={m.id === value}
>
<span className="pk-model-sel-dot" style={{ backgroundColor: m.color }} />
<span className="pk-model-sel-info">
<span className="pk-model-sel-item-name">{m.name}</span>
<span className="pk-model-sel-item-desc">{m.description}</span>
</span>
{m.id === value && <span className="pk-model-sel-check">✓</span>}
</button>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}Install
npm install framer-motion
This component requires framer-motion.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value* | string | — | Currently selected model id |
onChange* | (model: string) => void | — | Called when a model is selected |
models | ModelOption[] | — | Custom model list (defaults to 5 built-in models) |