# Model Selector

Two menus in the prompt input: one picks the model the agent runs, the other picks how much effort it spends.

Source: https://suiss.app/ui/docs/ai/model-selector

**React**

**Example**

```tsx
"use client"

import { ArrowUpIcon, CaretDownIcon, PlusIcon } from "@phosphor-icons/react"
import * as React from "react"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupTextarea,
} from "@/components/ui/input-group"

const providers = [
  {
    name: "Anthropic",
    models: [
      { id: "claude-opus-5-5", name: "Claude Opus 5.5" },
      { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5" },
    ],
  },
  {
    name: "OpenAI",
    models: [
      { id: "gpt-5", name: "GPT-5" },
      { id: "gpt-5-mini", name: "GPT-5 mini" },
    ],
  },
]

const models = providers.flatMap((provider) => provider.models)

const efforts = [
  { id: "low", name: "Low" },
  { id: "medium", name: "Medium" },
  { id: "high", name: "High" },
]

export default function ModelSelectorDemo() {
  const [text, setText] = React.useState("")
  const [model, setModel] = React.useState("claude-opus-5-5")
  const [effort, setEffort] = React.useState("medium")
  const formRef = React.useRef<HTMLFormElement>(null)
  const modelName = models.find((item) => item.id === model)?.name ?? model
  const effortName = efforts.find((item) => item.id === effort)?.name ?? effort
  const canSend = text.trim().length > 0

  function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (!canSend) {
      return
    }

    setText("")
  }

  return (
    <div className="w-full max-w-lg py-6">
      <form ref={formRef} onSubmit={onSubmit}>
        <InputGroup>
          <InputGroupTextarea
            value={text}
            onChange={(event) => setText(event.target.value)}
            placeholder="Ask the agent to change your code..."
            aria-label="Message"
            onKeyDown={(event) => {
              if (event.key === "Enter" && !event.shiftKey) {
                event.preventDefault()
                formRef.current?.requestSubmit()
              }
            }}
            className="p-3"
          />
          <InputGroupAddon align="block-end">
            <InputGroupButton
              size="icon-sm"
              variant="outline"
              className="rounded-full"
              aria-label="Attach files"
            >
              <PlusIcon />
            </InputGroupButton>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={
                  <InputGroupButton
                    size="sm"
                    variant="outline"
                    aria-label={`Model: ${modelName}`}
                    className="ms-auto"
                  />
                }
              >
                {modelName}
                <CaretDownIcon />
              </DropdownMenuTrigger>
              <DropdownMenuContent align="start" className="w-52">
                <DropdownMenuRadioGroup value={model} onValueChange={setModel}>
                  {providers.map((provider, index) => (
                    <React.Fragment key={provider.name}>
                      {index > 0 && <DropdownMenuSeparator />}
                      <DropdownMenuGroup>
                        <DropdownMenuLabel>{provider.name}</DropdownMenuLabel>
                        {provider.models.map((item) => (
                          <DropdownMenuRadioItem
                            key={item.id}
                            value={item.id}
                            closeOnClick
                          >
                            {item.name}
                          </DropdownMenuRadioItem>
                        ))}
                      </DropdownMenuGroup>
                    </React.Fragment>
                  ))}
                </DropdownMenuRadioGroup>
              </DropdownMenuContent>
            </DropdownMenu>
            <DropdownMenu>
              <DropdownMenuTrigger
                render={
                  <InputGroupButton
                    size="sm"
                    variant="outline"
                    aria-label={`Effort: ${effortName}`}
                  />
                }
              >
                <span className="text-muted-foreground">Effort</span>
                {effortName}
                <CaretDownIcon />
              </DropdownMenuTrigger>
              <DropdownMenuContent align="start" className="w-40">
                <DropdownMenuGroup>
                  <DropdownMenuLabel>Effort</DropdownMenuLabel>
                  <DropdownMenuRadioGroup
                    value={effort}
                    onValueChange={setEffort}
                  >
                    {efforts.map((item) => (
                      <DropdownMenuRadioItem
                        key={item.id}
                        value={item.id}
                        closeOnClick
                      >
                        {item.name}
                      </DropdownMenuRadioItem>
                    ))}
                  </DropdownMenuRadioGroup>
                </DropdownMenuGroup>
              </DropdownMenuContent>
            </DropdownMenu>
            <InputGroupButton
              type="submit"
              size="icon-sm"
              variant="default"
              className="rounded-full"
              disabled={!canSend}
              aria-label="Send"
            >
              <ArrowUpIcon />
            </InputGroupButton>
          </InputGroupAddon>
        </InputGroup>
      </form>
    </div>
  )
}
```

The model selector is two [Dropdown Menu](https://suiss.app/ui/docs/components/dropdown-menu) radio groups in the [Prompt Input](https://suiss.app/ui/docs/ai/prompt-input). One picks the model, grouped by provider, and the other picks the reasoning effort. Each trigger shows the current choice.

## Installation

The model selector is built with Dropdown Menu and Input Group.

```bash title="Terminal"
npx shadcn@latest add @esuiss/dropdown-menu @esuiss/input-group
```

Or use the [suiss/ui CLI](https://suiss.app/ui/docs/cli):

```bash title="Terminal"
npx @esuiss/ui@latest add dropdown-menu input-group
```

## Usage

```tsx
<DropdownMenu>
  <DropdownMenuTrigger render={<InputGroupButton size="sm" variant="outline" />}>
    {modelName}
    <ChevronDownIcon />
  </DropdownMenuTrigger>
  <DropdownMenuContent align="start">
    <DropdownMenuRadioGroup value={model} onValueChange={setModel}>
      <DropdownMenuGroup>
        <DropdownMenuLabel>Anthropic</DropdownMenuLabel>
        <DropdownMenuRadioItem value="claude-opus-5-5" closeOnClick>
          Claude Opus 5.5
        </DropdownMenuRadioItem>
      </DropdownMenuGroup>
    </DropdownMenuRadioGroup>
  </DropdownMenuContent>
</DropdownMenu>
```

Set `closeOnClick` on each radio item so the menu closes once a model is picked.

Give each trigger an `aria-label` that includes the current choice, such as "Model: Claude Opus 5.5", so the value is announced along with what the menu changes.

## AI SDK

Send the model and the effort with the message. Map them to a model and provider options in the route.

```tsx
sendMessage({ text }, { body: { model, effort } })
```

```ts title="app/api/chat/route.ts"
import { anthropic } from "@ai-sdk/anthropic"
import { openai } from "@ai-sdk/openai"
import { convertToModelMessages, streamText, type UIMessage } from "ai"

const models = {
  "claude-opus-5-5": anthropic("claude-opus-5-5"),
  "claude-sonnet-5-5": anthropic("claude-sonnet-5-5"),
  "gpt-5": openai("gpt-5"),
  "gpt-5-mini": openai("gpt-5-mini"),
}

export async function POST(req: Request) {
  const { messages, model, effort } = (await req.json()) as {
    messages: UIMessage[]
    model: keyof typeof models
    effort: "low" | "medium" | "high"
  }

  const result = streamText({
    model: models[model] ?? models["claude-opus-5-5"],
    messages: await convertToModelMessages(messages),
    providerOptions: {
      anthropic: { effort },
      openai: { reasoningEffort: effort },
    },
  })

  return result.toUIMessageStreamResponse()
}
```

Each provider reads its own key in `providerOptions` and ignores the rest.

## API Reference

See [Dropdown Menu](https://suiss.app/ui/docs/components/dropdown-menu#api-reference) and [Input Group](https://suiss.app/ui/docs/components/input-group#api-reference).

**React Native, SwiftUI, Jetpack Compose**

```tsx
import { CaretDownIcon } from "phosphor-react-native"
import * as React from "react"
import { View } from "react-native"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Icon } from "@/components/ui/icon"
import { Text } from "@/components/ui/text"

const providers = [
  { name: "Anthropic", models: ["Claude Opus 5.5", "Claude Sonnet 5.5"] },
  { name: "OpenAI", models: ["GPT-5", "GPT-5 mini"] },
]

function ModelSelector() {
  const [model, setModel] = React.useState("Claude Opus 5.5")
  return (
    <View className="items-start">
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" size="sm" />}>
          <Text>{model}</Text>
          <Icon as={CaretDownIcon} />
        </DropdownMenuTrigger>
        <DropdownMenuContent className="w-56" align="start">
          {providers.map((provider, index) => (
            <View key={provider.name}>
              {index > 0 ? <DropdownMenuSeparator /> : null}
              <DropdownMenuLabel>{provider.name}</DropdownMenuLabel>
              <DropdownMenuRadioGroup value={model} onValueChange={setModel}>
                {provider.models.map((name) => (
                  <DropdownMenuRadioItem key={name} value={name}>
                    <Text>{name}</Text>
                  </DropdownMenuRadioItem>
                ))}
              </DropdownMenuRadioGroup>
            </View>
          ))}
        </DropdownMenuContent>
      </DropdownMenu>
    </View>
  )
}

export function AiModelSelectorExample() {
  return (
    <ModelSelector />
  )
}
```

```swift
import EsuissUI
import PhosphorSwift
import SwiftUI

struct ModelSelectorExample: View {
  @State private var model = "Claude Opus 5.5"

  var body: some View {
    DropdownMenu {
      DropdownMenuLabel("Anthropic")
      DropdownMenuRadioGroup(selection: $model) {
        DropdownMenuRadioItem("Claude Opus 5.5", value: "Claude Opus 5.5")
        DropdownMenuRadioItem("Claude Sonnet 5.5", value: "Claude Sonnet 5.5")
      }
      DropdownMenuSeparator()
      DropdownMenuLabel("OpenAI")
      DropdownMenuRadioGroup(selection: $model) {
        DropdownMenuRadioItem("GPT-5", value: "GPT-5")
        DropdownMenuRadioItem("GPT-5 mini", value: "GPT-5 mini")
      }
    } label: {
      HStack(spacing: 6) {
        Text(verbatim: model)
        Ph.caretDown.regular.frame(width: 14, height: 14)
      }
    }
    .buttonStyle(.variant(.outline, size: .sm))
  }
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.esuiss.ui.components.Button
import com.esuiss.ui.components.ButtonSize
import com.esuiss.ui.components.ButtonVariant
import com.esuiss.ui.components.DropdownMenu
import com.esuiss.ui.components.DropdownMenuLabel
import com.esuiss.ui.components.DropdownMenuRadioGroup
import com.esuiss.ui.components.DropdownMenuRadioItem
import com.esuiss.ui.components.DropdownMenuSeparator
import com.esuiss.ui.components.PopupAlign

@Composable
fun ModelSelectorAIDefault() {
    var open by rememberSaveable { mutableStateOf(false) }
    var model by rememberSaveable { mutableStateOf("Claude Opus 5.5") }
    Column {
        DropdownMenu(
            expanded = open,
            onExpandedChange = { open = it },
            trigger = { Button(model, onClick = { open = !open }, variant = ButtonVariant.Outline, size = ButtonSize.Sm) },
            modifier = Modifier.width(224.dp),
            align = PopupAlign.Start,
        ) {
            DropdownMenuLabel("Anthropic")
            DropdownMenuRadioGroup(value = model, onValueChange = { model = it }) {
                DropdownMenuRadioItem("Claude Opus 5.5", "Claude Opus 5.5")
                DropdownMenuRadioItem("Claude Sonnet 5.5", "Claude Sonnet 5.5")
            }
            DropdownMenuSeparator()
            DropdownMenuLabel("OpenAI")
            DropdownMenuRadioGroup(value = model, onValueChange = { model = it }) {
                DropdownMenuRadioItem("GPT-5", "GPT-5")
                DropdownMenuRadioItem("GPT-5 mini", "GPT-5 mini")
            }
        }
    }
}
```

Model Selector on mobile is built from the same parts as on the web: [Dropdown Menu](https://suiss.app/ui/docs/components/dropdown-menu), [Button](https://suiss.app/ui/docs/components/button). The example above is a static demo; connect it to your agent by rendering the same parts from your messages.

## Installation

**React Native**

```bash title="Terminal"
npx @esuiss/ui@latest add dropdown-menu button
```

**SwiftUI**

```bash title="Terminal"
npx @esuiss/ui@latest add dropdown-menu button --platform swift
```

**Jetpack Compose**

```bash title="Terminal"
npx @esuiss/ui@latest add dropdown-menu button --platform kotlin
```

## API Reference

See [Dropdown Menu](https://suiss.app/ui/docs/components/dropdown-menu), [Button](https://suiss.app/ui/docs/components/button).

