Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

AI Kit

Model Picker

AI Kit

Model Picker React component for Tailwind CSS v4

Matrix OrbPrompt Input
Live props
State
defaultValue
defaultOpen
disabled
loading
Appearance
variant
size
radius
tone
color
showIcon
showProvider
showCapabilities
showSpeed
showCost
showContext
showDescription
Layout
fullWidth
contentWidth320px
maxHeight560px
side
align
Behavior
groupBy
search
responsive
shortcut
name
locale
Code
import { ModelPicker } from "@/components/ui/model-picker"

<ModelPicker />
  1. Home
  2. Components
  3. AI Kit
  4. Model Picker
Pro

Model Picker React component for Tailwind CSS v4

v1.0.1·Updated in 1.6.0

A React AI model picker for chat apps: a quiet trigger that opens a short grouped list of models, each with one line on what it is for.

Click or tap the trigger ("Northwind Standard" with a chevron) to open the list; the chevron turns over on a quick spring. On desktop it is a popover that opens upward from a composer, grouped by model group with sticky section labels; each row shows the model's icon, its name with New / Recommended badges and one line of description; showCapabilities, showSpeed, showCost and showContext add a line of capability icons, speed and cost meters and the context window, whose icons and meters name themselves on hover. Click a row to pick it: the check draws, the list closes and onValueChange gets the id and the model. Locked models stay in the list, dimmed with a lock and the reason as their description, and cannot be picked. Above 8 models (or with search="always") a search row sits on top and takes focus: it matches names, makers, groups, capability names and keywords, ignores case and diacritics and bolds the matched letters. Keyboard: Tab reaches the trigger; Enter, Space or ArrowDown open it on the chosen model, ArrowUp and ArrowDown move, Home and End jump to the ends, typing letters jumps by name, Enter picks, Escape or Tab close and focus stays on the trigger. While closed, letters pick the next model by name like a native select. With shortcut ("mod+.") the list toggles from anywhere on the page, the tooltip shows the keys, and a model picked after the shortcut sends focus back where you were typing. On a phone the list is a bottom sheet titled "Model" with 64px rows, two-line descriptions and no tooltips; a tap picks and closes it, a drag down or a tap outside dismisses it. With reduced motion the chevron flips without the spring and the list fades instead of scaling.

Model Picker is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add model-picker, together with Select, Badge, Tooltip, Spinner, Field and 9 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add model-picker

Model Picker is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import ModelPicker from @/components/ui/model-picker.

import { useState } from "react"
import { Feather, Gem, Sparkles } from "lucide-react"
import { ModelPicker, type AiModel } from "@/components/ui/model-picker"

const MODELS: AiModel[] = [
  { id: "swift", name: "Northwind Swift", group: "Northwind AI", description: "Short answers, almost instant", icon: <Feather />, capabilities: ["tools", "files"], contextWindow: 128_000, speed: 3, cost: 1 },
  { id: "standard", name: "Northwind Standard", group: "Northwind AI", description: "Balanced, for everyday work", icon: <Sparkles />, capabilities: ["vision", "tools", "files", "web"], contextWindow: 200_000, speed: 2, cost: 2, recommended: true },
  { id: "pro", name: "Northwind Pro", group: "Northwind AI", description: "Deep reasoning for analysis and reports", icon: <Gem />, capabilities: ["reasoning", "tools", "web", "files", "code", "vision"], contextWindow: 200_000, speed: 1, cost: 3, isNew: true },
]

export function ChatModel() {
  const [model, setModel] = useState("standard")
  return (
    <ModelPicker
      models={MODELS}
      value={model}
      onValueChange={(id) => setModel(id)}
      shortcut="mod+."
      labels={{ placeholder: "Pick a model", sheetTitle: "Switch model" }}
    />
  )
}

Props

  • models*
    AiModel[]

    The models: { id, name, provider?, description?, icon?, group?, capabilities?, contextWindow?, speed?: 1 | 2 | 3, cost?: 1 | 2 | 3, pricing?, badge?: { label, tone? }, recommended?, isNew?, disabled?, disabledReason?, keywords? } from lib/ai-chat. A repeated id keeps its first model (with a warning while developing).

  • value
    string

    The chosen model's id, controlled. "" shows the placeholder, and so does an id that is not among the models; the picker never replaces it on its own.

  • defaultValuedefault ""
    string

    The starting model while uncontrolled. "" is automatic: the first recommended model, else the first enabled one (it follows models that arrive later).

  • onValueChange
    (id: string, model: AiModel) => void

    Called with the id and the whole model after a pick or a typeahead jump. Never called for the automatic default.

  • defaultOpendefault false
    boolean

    Starts with the list (or the sheet) open. Pass open and onOpenChange to control it.

  • open
    boolean

    Whether the list is open, controlled. It never opens while disabled.

  • onOpenChange
    (open: boolean) => void

    Called when the list opens or closes.

  • disableddefault false
    boolean

    Dims the trigger; it never opens and the shortcut does nothing. A disabled Field around it does the same.

  • loadingdefault false
    boolean

    Models are on their way: a spinner replaces the chevron and the list shows a loading row until models arrive.

  • variantdefault "ghost"
    "ghost" | "soft" | "outline"

    ghost for composers and toolbars (a tint on hover and while open); soft and outline copy the field box, so the picker lines up with inputs and selects in a form.

  • sizedefault "sm"
    "sm" | "md" | "lg"

    Trigger height 32 / 40 / 48px with matching text and icons. md grows to 44px on touch, sm keeps its look with a 44px hit area.

  • radiusdefault "full"
    "none" | "sm" | "md" | "lg" | "full"

    The trigger's corners, scaled per size like the button; full is a pill.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Paints the trigger's text, tint and border in a tone. neutral keeps the field look; brand is for the one place a premium model should stand out.

  • colordefault ""
    string

    Any CSS color for the trigger; replaces the tone. Leave empty to use the tone.

  • showIcondefault true
    boolean

    The chosen model's icon before its name in the trigger (when the model has one).

  • showProviderdefault false
    boolean

    The provider before the name in the trigger: "Northwind AI · Standard" (a name that repeats the maker is shortened).

  • showCapabilitiesdefault false
    boolean

    The row of capability icons in each option (vision, tools, reasoning, web, files, code, image, voice), each with a tooltip.

  • showSpeeddefault false
    boolean

    The 3-step speed meter in each option.

  • showCostdefault false
    boolean

    The 3-step cost meter in each option.

  • showContextdefault false
    boolean

    The context window in each option ("200K context"), formatted for the locale.

  • showDescriptiondefault true
    boolean

    One line of description in the menu, two in the phone sheet. A locked model shows its reason either way.

  • fullWidthdefault false
    boolean

    Stretches the trigger to its container with the chevron at the end, the usual choice as a form field.

  • contentWidthdefault 320
    number

    The list's width on desktop in px (never narrower than the trigger, never wider than the screen).

  • maxHeightdefault 560
    number

    The list's maximum height on desktop in px, capped by the room on screen; longer lists scroll with sticky group labels.

  • sidedefault "top"
    "top" | "right" | "bottom" | "left"

    Where the list opens. Composers sit low on the screen, so it opens upward; it flips when there is no room.

  • aligndefault "start"
    "start" | "center" | "end"

    How the list lines up with the trigger.

  • container
    HTMLElement | null

    Where the list or the sheet is portaled (document.body by default).

  • groupBydefault "group"
    "group" | "provider" | "none"

    List sections: by model.group, by model.provider, or one flat list. Models without one go last, under a separator with no visible label.

  • searchdefault "auto"
    "auto" | "always" | "never"

    The search row on top of the list: auto shows it above 8 models. It matches names, providers, groups, capability names and keywords, diacritics ignored.

  • responsivedefault true
    boolean

    A bottom sheet with 64px rows and two-line descriptions below 768px instead of the popover.

  • shortcutdefault ""
    string

    A key combo in mod+k notation that toggles the list from anywhere ("mod+." is Cmd+. on a Mac, Ctrl+. elsewhere). Shown in the trigger's tooltip and set as aria-keyshortcuts. Empty binds nothing.

  • namedefault ""
    string

    Renders a hidden native select with this name, so FormData and form reset work.

  • localedefault ""
    string

    The locale of the context window numbers ("200K" in en-US, "200 K" and "1 mil." in ro-RO). Empty uses the UIProvider's, then the browser's.

  • labels
    Partial<ModelPickerLabels>

    Every string, English by default (DEFAULT_MODEL_PICKER_LABELS): placeholder "Choose a model", trigger "Model" (the accessible name is "Model: Northwind Standard"), tooltip "Switch model", search "Search models", empty "No models found", loading, sheetTitle "Model", recommended, new, context (formatted) => "200K context", speed ["Slower", "Fast", "Fastest"], cost ["Low cost", "Medium cost", "High cost"], unavailable, ungrouped, results (count) => string, clearSearch, capabilities (DEFAULT_AI_CAPABILITY_LABELS).

  • classNames
    Partial<Record<"trigger" | "icon" | "provider" | "name" | "chevron" | "content" | "search" | "group" | "option" | "badges" | "description" | "meta" | "capabilities", string>>

    Extra classes for the inner parts: the trigger and its pieces, the list surface (content), its search row and group labels, and the parts of each option.

  • className
    string

    Extra classes for the trigger button, merged last. Other props (aria-*, data-*, onClick, onBlur, id, form, ref) go there too.

Parts

  • ModelPickerOption

    component

    The rich row on its own, for other lists (a command menu, a settings table): icon tile or provider initials, name and badges, description, capabilities, meters and context, a lock for locked models. Props: { model, density?: "menu" | "sheet", ranges?, showCapabilities?, showSpeed?, showCost?, showContext?, showDescription?, tooltips?, locale?, labels?, classNames?, ...div }.

  • ModelCapabilities

    component

    The capability icons as a list; each name is in a tooltip on fine pointers and always there for screen readers. Props: { capabilities: AiCapability[], labels?, tooltips?, size?: "sm" | "md", classNames?, ...ul }.

  • ModelMeter

    component

    The 3-step speed or cost indicator: a Zap or Coins glyph and three bars, role="img" with the level's name. Props: { value: 1 | 2 | 3, kind?: "speed" | "cost", label?, icon?, tooltip?, classNames?, ...span }.

  • toModelOptions

    function

    Models to select / listbox options, grouped, with provider, group and capability names as search keywords. Pair it with ModelPickerOption in a Listbox or Combobox renderOption. Signature: (models: AiModel[], { groupBy?, showDescription?, labels? }) => SelectItems.

  • DEFAULT_MODEL_PICKER_LABELS

    constant

    The English strings, to spread and override. Type: ModelPickerLabels.

Examples

As a form field

variant="outline" size="md" fullWidth inside a Field: same height, border and radius as the select under it.

Flat list with search

groupBy="none" search="always" over 14 models; names, makers and capabilities all match.

Detailed rows

showCapabilities, showSpeed, showCost and showContext add a line of model details under each row.

Keyboard shortcut

shortcut="mod+." toggles the list from anywhere and shows in the trigger's tooltip.

Brand

tone="brand" on the trigger and a model.badge in the brand tone.

Sizes and variants

ghost for composers, soft and outline as fields; 32, 40 and 48px.

States

Loading, disabled, a value that is not among the models, and a plan where every model is locked.

Dependencies

motionlucide-reactselectbadgetooltipspinnerfieldai-chattext-matchuse-controllable-stateuse-localeuse-media-queryuse-shortcutmotiontonesui-config

Files

  • components/ui/model-picker.tsx

FAQ

Is the React Model Picker component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Model Picker?

Run npx wingo-ui@latest add model-picker. It also adds the Wingo UI items it builds on (Select, Badge, Tooltip, Spinner, Field and 9 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/model-picker.

How does Model Picker behave on phones?

A bottom sheet with 64px rows and two-line descriptions below 768px instead of the popover. The responsive prop controls this and is on by default.

See also

SelectComboboxPrompt InputAI ChatlibBadge

Used in

Prompt InputAI Chat Page

More React AI components

Agent Status CardChat MessageChat ThreadChat WidgetConversation ListReasoning Indicator
All 17 React AI components

Articles using this component

  • React Prompt Input: Enter, IME, Files, Stop and Voice

    Building UI with AI agents · Oct 16, 2026 · 10 min read

  • React AI Chat UI: Streaming, Auto-Scroll and Mobile

    Building UI with AI agents · Oct 9, 2026 · 10 min read

PreviousMatrix OrbNext Prompt Input

Hi Emma! How can I help?

Northwind Assistant, with your Northwind CRM data

Ask anything

standard · included in the plan

defaultValue="" search="auto" responsive=true locale="en-US"