Model picker
Lets people choose which model answers and how hard it thinks: an effort slider with a model list, a searchable list grouped by provider with a separate effort menu, a detailed list, or a quick three-way switch.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/model-picker-effort.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/model-picker-effort.json
Usage & API
Example
import { EffortPicker } from "@/components/ai/model-picker-effort"
<EffortPicker
models={[]}
model="…"
onModelChange={() => {}}
effort={effort}
onEffortChange={() => {}}
fast={true}
onFastChange={() => {}}
/>Props EffortPicker
| Prop | Type & description | Required |
|---|---|---|
models | ModelOption[] | Required |
model | string | Required |
onModelChange | (id: string) => void | Required |
effort | EffortLevel | Required |
onEffortChange | (effort: EffortLevel) => void | Required |
fast | boolean | Required |
onFastChange | (fast: boolean) => void | Required |
defaultEffort | EffortLevel | Optional |
initialView | ViewStart with a panel open, for demos. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Effort slider exports EffortPicker. Props are models, model, onModelChange, effort, onEffortChange, fast, onFastChange, defaultEffort and initialView. A model is an id, a name and a note. defaultEffort starts at Medium. initialView can be closed, effort or models, for demos. The trigger is a dialog button. The panel morphs between the effort view and the model list. Fast mode is a pressed toggle. Reset is labelled Reset to the default effort. The track is labelled Reasoning effort. Dragging or the arrow keys snap to the six stops. Home and End jump to the ends. In fast mode, bubbles drift through the fill, and more of them appear as the index rises. Without fast mode, small stops stay visible inside the fill. Ultra uses a violet gradient. The model list's first row shows its note inline. Other notes appear in a tooltip beside the row, hidden below the sm breakpoint. Choosing a model returns to the effort view. The first demo model is Default, with the note Picks a model for each task. A popover tries to stay inside the nearest data-popover-bounds element.
Example
import { T3Picker } from "@/components/ai/model-picker-t3"
<T3Picker
providers={[]}
model="…"
onModelChange={() => {}}
effort={effort}
onEffortChange={() => {}}
fast={true}
onFastChange={() => {}}
favorites={[]}
onFavoritesChange={() => {}}
/>Props T3Picker
| Prop | Type & description | Required |
|---|---|---|
providers | Provider[] | Required |
model | string | Required |
onModelChange | (id: string) => void | Required |
effort | Effort | Required |
onEffortChange | (effort: Effort) => void | Required |
fast | boolean | Required |
onFastChange | (fast: boolean) => void | Required |
favorites | string[] | Required |
onFavoritesChange | (ids: string[]) => void | Required |
defaultOpen | boolean | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
By provider exports T3Picker. Props are providers, model, onModelChange, effort, onEffortChange, fast, onFastChange, favorites, onFavoritesChange and defaultOpen. A provider has an id, a name, an icon, a color, an optional badge and models. A model can set short and legacy. The left trigger's accessible name is Model: plus the current name, and it shows the provider glyph and the short name. The right trigger is Effort: plus the level, and it adds fast mode on when fast is true, with a bolt. The model dialog is labelled Choose a model. A vertical tablist labelled Providers starts with Favorites, then one tab per provider. Search models filters provider name plus model name across the whole catalog. Legacy models is a row that shows the count and expands. Starring calls onFavoritesChange. The button reads Add or Remove, the model name, and to favorites or from favorites. An empty favorite tab says No favorites yet, and Star a model to keep it here. A failed search says Nothing matches. The effort dialog is labelled Effort and speed. Four bars form a radio group labelled Effort, with notes from Short answers, barely any thinking up to Takes its time on the hardest tasks. Hover previews a level without selecting it. Fast mode is a switch whose caption is Quicker replies, costs more.
Example
import { ModelPickerDropdown } from "@/components/ai/model-picker-dropdown"
<ModelPickerDropdown
models={[]}
value="…"
onChange={() => {}}
/>Props ModelPickerDropdown
| Prop | Type & description | Required |
|---|---|---|
models | ModelOption[] | Required |
value | string | Required |
onChange | (id: string) => void | Required |
defaultOpen | boolean | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Dropdown exports ModelPickerDropdown. Props are models, value, onChange and defaultOpen. Each model has an id, a name, a note, speed from 1 to 3, cost from 1 to 3, and an optional tag. The trigger shows the current name and a chevron that turns when open. Arrow keys on the trigger open the list. The listbox is labelled Model. Speed is three bolt icons, with an accessible name of Speed: N of 3. Cost is dollar signs, Cost: N of 3, the active ones in a darker zinc and the rest faded. A tag renders as a small pill. The demo marks Sonnet 5.5 with Default. The selected row gets a check. Highlight follows the pointer and the keyboard independently of selection, until Enter or Space commits and returns focus to the trigger. Escape and Tab close it. A pointer outside closes it too.
Example
import { ModelPickerSegmented } from "@/components/ai/model-picker-segmented"
<ModelPickerSegmented
value={value}
onChange={() => {}}
/>Props ModelPickerSegmented
| Prop | Type & description | Required |
|---|---|---|
value | Effort | Required |
onChange | (value: Effort) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Segmented exports ModelPickerSegmented, plus the efforts table. Props are only value and onChange, and value is fast, balanced or deep. The group is labelled Response mode. Fast is Haiku 5.5 and Quick answers in a second or two. Balanced is Sonnet 5.5 and Good for most questions. Deep is Opus 5.5 and Thinks longer before it answers. The button title joins the model and the hint. Icons are hidden below sm. Arrow keys wrap and move focus. The selected tab is the only one in the tab order. The demo sits the control in a fake composer, next to a Send message button that is not part of the picker, and prints the model and hint underneath in a live region that swaps with a short vertical motion. Changing the choice does not change any other prop, because there is no model id to report.
Choosing a layout
Use Effort slider when reasoning effort is the decision people make on every message and the model is a second step. The trigger shows the model name and the level, or the words Select effort while that panel is open. Use By provider when you have more than one vendor, favorites, and older models that should stay collapsed. Use Dropdown when a short list with speed and cost is enough and effort is not a separate choice. Use Segmented when you would rather offer three speeds than a catalog. It is the one to put in a toolbar next to Prompt input if a full menu is too much.
Prompt input's card only prints a model string. Its workspace menu is a short radio list of Low, Medium and High plus whatever model names you pass, with no notes and no favorites. Reach for Model picker when that menu is too small. Context meter does not belong here. It shows tokens, not a choice. The pickers also do not disable themselves when a model is down. You would swap the whole control, or ignore a choice, in the parent.
Why does effort mean something different on Effort slider and By provider?
They are different types. Effort slider's EffortLevel is Minimal, Low, Medium, High, Extra high or Ultra, and the slider's aria-valuetext is that word. By provider's Effort is Low, Medium, High or Max, chosen by clicking a bar, with a sentence under the bars that changes on hover before you commit. Segmented does not use either scale. If you store one effort field in your app, map it at the boundary. Do not pass Ultra into T3Picker.
Does the shortcut on By provider still work when the menu is closed?
No. The Ctrl or Command plus 1 to 9 listener is attached only while a menu is open, and it only fires for the model menu. It looks up the shortcut number painted on the current rows, which is at most the first nine model rows. A legacy-models row does not take a number. Favorites and a search each build their own row list, so the numbers follow whatever is on screen, not a fixed catalog order.