Selectors

Composite picker patterns built on top of Dropdown Menu. These are concrete UX templates — copy the structure when you need a similar shape, then adapt the data, icons, and copy.

Patterns

Model selector (tier picks + submenu of specific models)

Two top-level "tier" picks (Auto, Fast) with inline descriptions, then a More models submenu housing the specific provider models. The sub-trigger shows the active model name on the right when a model from the submenu is selected, so the user can see the current pick without diving in. Mirrors the real ChatModeSelector in apps/frontend.

Datasource selector (search + groups + switches)

Multi-toggle picker with fuzzy search across name and description, sources grouped by External / Internal, and a Switch per row. Clicking the row or the switch flips state; closeOnClick={false} keeps the menu open so users can toggle several sources without reopening. Empty-state copy ("No sources found") renders when the query filters out everything.

Settings toggles (icon + switch)

The simplest end of this family — single-row preference toggles with an icon on the left, a switch on the right, and a separator splitting groups (Notifications, Privacy). Use this shape when the settings live inside a quick-access menu rather than a full settings page.

When to use each shape

ShapeWhen
Model selectorOne-of-many pick where the options have meaningful differences. Use submenus when the long tail is large but a few tiers cover most cases.
Datasource selectorMulti-toggle where each option is independent and the list can grow beyond ~8 items. Always include search once the list crosses ~10.
Settings togglesA handful of independent on/off prefs that need immediate effect. If you have more than ~6, move them into a settings page instead.

Recipes

Surface the deep-nested selection on the parent

When a submenu houses options the user picks rarely, show the active pick as a tertiary hint on the sub-trigger so the parent menu still communicates state at a glance.

tsx
<DropdownMenuSubTrigger>
  <span className="grow">More models</span>
  {isMoreModelSelected && (
    <span className="text-tertiary mr-1 text-xs">{selected.name}</span>
  )}
</DropdownMenuSubTrigger>

Search that filters across multiple fields

DropdownMenuSearch doesn't ship with built-in filtering — you bring the logic. For richer datasets, configure Fuse with keys so the search spans more than just the visible label:

tsx
const FUSE_OPTIONS = {
  threshold: 0.4,
  ignoreLocation: true,
  keys: ["name", "description"],
} as const;

const fuse = useMemo(() => new Fuse(items, FUSE_OPTIONS), [items]);
const filtered = useMemo(() => {
  const q = query.trim();
  if (q === "") return items;
  return fuse.search(q).map((r) => r.item);
}, [fuse, query, items]);

Switch row that toggles via row click OR switch click

Wire onSelect on the row AND onCheckedChange on the switch to the same setter. closeOnClick={false} keeps the menu open so the user can flip several:

tsx
<DropdownMenuItem
  closeOnClick={false}
  onSelect={() => onCheckedChange(!checked)}
  prefix={<Icon className="text-tertiary" />}
  suffix={<Switch checked={checked} onCheckedChange={onCheckedChange} aria-label={name} />}
>
  <div className="flex min-w-0 flex-col leading-tight">
    <span className="text-primary truncate">{name}</span>
    <span className="text-tertiary truncate text-xs">{description}</span>
  </div>
</DropdownMenuItem>

Group + empty state under search

When you split filtered results into groups, hide the group labels when their bucket is empty so the menu doesn't show a bare label with nothing under it. Show a single empty state if every group is empty.

tsx
{filtered.length === 0 ? (
  <div className="text-tertiary px-2 py-6 text-center text-sm">No sources found</div>
) : (
  <>
    {external.length > 0 && (
      <>
        <DropdownMenuLabel>External</DropdownMenuLabel>
        {external.map(renderRow)}
      </>
    )}
    {external.length > 0 && internal.length > 0 && <DropdownMenuSeparator />}
    {internal.length > 0 && (
      <>
        <DropdownMenuLabel>Internal</DropdownMenuLabel>
        {internal.map(renderRow)}
      </>
    )}
  </>
)}

See also

  • Dropdown Menu — the primitive these patterns are built on.
  • Switch — the right-slot control used in the toggle-based selectors.
PreviousSelect
Made in NYC© 2026 Rogo Technologies Inc.