Skip to content
Neon UI
Esc
navigateopen⌘Jpreview
On this page

ModelSelect

Model picker for agent chat input bars, grouped by provider.

ModelSelect is the model picker for an agent chat input bar. Models are grouped by provider with optional provider logos, capability tags, and the mono gateway id alongside each entry. For the combined model + reasoning control, see ThinkingModelSelect. Presentational: pass models as props. Fixtures snapshot the live Neon catalog, and the typed example fetches the neon provider from models.dev — the same code works with any models.dev gateway. Models with reasoning: false hide the thinking footer automatically.

Install

npx shadcn@latest add https://ui.neon.com/r/model-select.json

Usage

import { ModelSelect } from "@/components/neon-ui/model-select";

<ModelSelect
  models={[
    {
      id: "neon/claude-sonnet-4-6",
      name: "Claude Sonnet 4.6",
      provider: "Anthropic",
    },
    { id: "neon/gpt-5.2", name: "GPT-5.2", provider: "OpenAI" },
  ]}
  value={model}
  onValueChange={setModel}
/>;

Wiring to Neon

The Neon AI Gateway catalog is published as the neon provider on models.dev — no auth required, and because it’s the models.dev format, the same wiring works with any other gateway in the catalog by swapping the provider key:

const response = await fetch("https://models.dev/api.json");
const { neon } = await response.json();

const models = Object.values(neon.models).map((model) => ({
  id: model.id,
  name: model.name,
  provider: providerOf(model.family),
  reasoning: model.reasoning,
}));

The catalog itself is maintained as per-model TOML in the models.dev repo, so new gateway models land in the API as they ship.

To list only the models enabled for a specific project, use the gateway’s OpenAI-compatible GET /v1/models with a bearer token instead — behind a server proxy, since the token must not ship to the browser. The use-gateway-models hook wraps that pattern: it fetches the catalog (either the raw { data } shape or a pre-shaped { models } proxy response) and keeps the selection valid against it — explicit pick first, then a preferred default, then the first available model.

const { model, models, setModel } = useGatewayModels({
  defaultModel: "claude-sonnet-4-6",
  endpoint: "/api/models",
});

return <ModelSelect models={models} onValueChange={setModel} value={model} />;

Never hardcode a default id without one of these guards — a given gateway branch may not have that model enabled, and an unlisted id submits a request the gateway rejects. With plain ModelSelect, fallbackToFirst is the one-prop version of the same guard.

Props

PropType
modelsAiModel[]

Models to list; grouped by provider in listed order. Each has id, name, provider, and an optional tag.

TypeAiModel[]
value?string

Controlled selected model id.

Typestring
defaultValue?string

Uncontrolled initial model id.

Typestring
onValueChange?(value: string) => void

Called with the newly selected model id.

Type(value: string) => void
placeholder?ReactNode

Trigger content while nothing is selected.

TypeReactNode
Default"Select model"
size?"sm" | "md" | "lg"

Trigger size: compact, default, or roomy.

Type"sm" | "md" | "lg"
Default"md"
footer?ReactNode

Pinned footer rendered below the scrolling list.

TypeReactNode
valueSuffix?ReactNode

Extra content rendered after the model name in the trigger.

TypeReactNode
logos?Record<string, ReactNode>

Provider logos keyed by provider name. Without a logo, no mark is rendered.

TypeRecord<string, ReactNode>
excludeModels?string[]

Model ids to hide from the list.

Typestring[]
fallbackToFirst?boolean

Controlled usage only: when the current value is not in models, call onValueChange with the first available model instead of sitting on a placeholder.

Typeboolean

Accessibility

Built on the Base UI Select primitive: full keyboard support (arrows, type-ahead, Home/End), aria-label="Model" on the trigger, and provider marks are decorative (aria-hidden).