affectUI › Components › Chat

Model Picker

The model menu of an AI chat – point at a model and its speed, quality and cost bars fill, switch on extended thinking and its effort unfolds, while the tray rolls the first-token time and price.

Part of affectUI Pro. Install: npx shadcn@latest add https://www.affectui.com/r/model-picker.json · Import from @/components/ui/model-picker

When to use

  • The model switch in the toolbar of a chat composer – the place people decide between fast, cheap and smart
  • Products with a few models that differ in speed, quality and price, and settings that ride along with the model: thinking, effort, web search
  • When people should see what a choice costs them before they send – the tray shows first-token time and price

When not to use

  • Dozens of models from many providers – use Combobox or Command Menu, where people can type to search
  • A setting page that lists every parameter – this menu is for the two or three things people change between messages
  • A plain two-way choice – Segmented Control says it with less

Usage

import { ModelPicker } from "@/components/ui/model-picker";

const models = [
  { id: "opus", name: "Opus 5.5", description: "Hardest problems", speed: 2, quality: 5, cost: 5, latencyMs: 2100, pricePerM: 25 },
  { id: "sonnet", name: "Sonnet 5.5", description: "Everyday work", speed: 4, quality: 4, cost: 3, latencyMs: 900, pricePerM: 15 },
  { id: "haiku", name: "Haiku 5.5", description: "Quick and light", speed: 5, quality: 3, cost: 1, latencyMs: 400, pricePerM: 4 },
];

export function ComposerToolbar() {
  return (
    <div className="toolbar">
      <ModelPicker models={models} defaultValue="sonnet" />
      <SendButton />
    </div>
  );
}

Props

NameTypeDefaultDescription
modelsPickerModel[]{ id, name, description?, icon?, speed?, quality?, cost?, latencyMs?, pricePerM? } – strengths go from 1 to 5 (cost 5 is the most expensive); leave all three out for a router like Auto and the row says "Varies"
valuestringId of the chosen model (controlled)
defaultValuestringfirst modelChosen model when uncontrolled
onChange(id: string) => voidCalled with the picked model's id
thinkingbooleanExtended thinking switch (controlled)
defaultThinkingbooleanfalseStarting state of the thinking switch
onThinkingChange(on: boolean) => voidCalled when the thinking switch flips
webSearchbooleanWeb search switch (controlled)
defaultWebSearchbooleanfalseStarting state of the web search switch
onWebSearchChange(on: boolean) => voidCalled when the web search switch flips
effort"low" | "medium" | "high"Thinking effort (controlled) – the control only shows while thinking is on
defaultEffort"low" | "medium" | "high""medium"Starting effort
onEffortChange(effort) => voidCalled with the new effort
openbooleanControlled open state of the menu
defaultOpenbooleanfalseStarts open when uncontrolled
onOpenChange(open: boolean) => voidCalled when the menu opens or closes
align"start" | "end""start"Which edge of the trigger the menu lines up with
side"bottom" | "top""bottom"Opens below or above the trigger – use top for a composer at the bottom of the screen
classNamestringExtra class on the root

Keyboard

  • Enter / Space / ↓: On the trigger: opens the menu and moves into the model list
  • ↑ / ↓ / Home / End: In the list: moves between models – the tray follows
  • Enter / Space: In the list: picks the model and closes the menu
  • Tab: From the list to Extended thinking, Effort and Web search
  • ← / →: In Effort: Low, Medium, High
  • Esc: Closes the menu and returns to the trigger

Accessibility

  • The trigger has aria-haspopup, aria-expanded and aria-controls; its name carries the model and whether thinking is on
  • Models are a listbox with options and aria-activedescendant, so focus stays on the list while the arrows move
  • Thinking and web search are switches, Effort is a radio group with one tab stop
  • While closed the menu is inert; the tray's numbers are announced politely once, not while they roll

Responsive

  • The menu is 320px wide and never wider than the screen minus 32px. Below 300px the row glyphs and key hints give way and the bars move closer
  • Touch screens: taller trigger, rows, switches and effort buttons; the key hints are hidden
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Chat Composer – Prompt composer with model picker, voice input, app connections and approval request
  • Combobox – Pick one or many from a searchable, grouped list – chips with avatars, highlighted matches, create-on-the-fly and full keyboard support
  • Segmented Control – Switch between two to six options – a raised pill or underline that glides, stretches a little and can be dragged across, with icons, counts and badges
  • Context Window – What fills the model's context, as a field of cells – hover a part to light it up, remove files, and compact the conversation while its cells shimmer and clear

Guides