Typedora UI
Components

TypedCombobox

A headless, type-safe combobox component with search functionality, full TypeScript inference, and customizable rendering.

Introduction

TypedCombobox is a headless, type-safe combobox component built on top of Popover and Command components. It provides full TypeScript inference for option values with built-in search/filter functionality.

Key features:

  • Full type-safety for string, number, and boolean values
  • Built-in search/filter with customizable filter function
  • Headless design with render props for complete customization
  • Support for grouped options
  • Controlled and uncontrolled usage

Installation

npx shadcn@latest add https://typedora-ui.netlify.app/r/typed-combobox.json
pnpm dlx shadcn@latest add https://typedora-ui.netlify.app/r/typed-combobox.json
npx shadcn@latest add https://typedora-ui.netlify.app/r/typed-combobox.json
bunx --bun shadcn@latest add https://typedora-ui.netlify.app/r/typed-combobox.json

Usage

Basic Usage

basic-usage.tsx
import {  } from "@/components/typed-combobox";

export function () {
  return (
    <
      ="Select a framework..."
      ="Search frameworks..."
      ={
        [
          { : "Next.js", : "nextjs" },
          { : "Remix", : "remix" },
          { : "Astro", : "astro" },
          { : "SvelteKit", : "sveltekit" },
          { : "Nuxt", : "nuxt" },
        ] as 
      }
      ={(value) => {
value: "nextjs" | "remix" | "astro" | "sveltekit" | "nuxt"
.(); }} /> ); }

Number Values

number-values.tsx
import {  } from "@/components/typed-combobox";

export function () {
  return (
    <
      ="Select priority..."
      ="Search priorities..."
      ={
        [
          { : "Low", : 1 },
          { : "Medium", : 2 },
          { : "High", : 3 },
          { : "Critical", : 4 },
        ] as 
      }
      ={(value) => {
value: 1 | 2 | 3 | 4
.(); }} /> ); }

Boolean Values

boolean-values.tsx
import {  } from "@/components/typed-combobox";

export function () {
  return (
    <
      ="Select status..."
      ="Search..."
      ={
        [
          { : "Active", : true },
          { : "Inactive", : false },
        ] as 
      }
      ={(value) => {
value: boolean
.(); }} /> ); }

Custom Item Rendering

custom-item.tsx
import {  } from "lucide-react";
import {  } from "@/components/typed-combobox";
import {  } from "@/lib/utils";

const  = [
  { : 1, : "John Doe", : "john@example.com", : "JD" },
  { : 2, : "Jane Smith", : "jane@example.com", : "JS" },
  { : 3, : "Bob Johnson", : "bob@example.com", : "BJ" },
] as ;

export function () {
  return (
    <
      ="Select a user..."
      ="Search users..."
      ={}
      ="id"
      ="name"
      ={300}
      ={({ , , ,  }) => (
        < {...} ="flex items-center gap-3 px-2 py-2">
          < ="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-sm font-medium">
            {.}
          </>
          < ="flex-1 min-w-0">
            < ="font-medium truncate">{.}</>
            < ="text-xs text-muted-foreground truncate">
              {.}
            </>
          </>
          <
            ={(
              "h-4 w-4 shrink-0",
               ? "opacity-100" : "opacity-0",
            )}
          />
        </>
      )}
      ={() => {
        .();
      }}
    />
  );
}

Grouped Options

grouped-options.tsx
import {  } from "@/components/typed-combobox";

const  = [
  {
    : "Frontend",
    : [
      { : "React", : "react" },
      { : "Vue", : "vue" },
      { : "Svelte", : "svelte" },
    ],
  },
  {
    : "Backend",
    : [
      { : "Node.js", : "nodejs" },
      { : "Python", : "python" },
      { : "Go", : "go" },
    ],
  },
] as ;

export function () {
  return (
    <
      ="Select a technology..."
      ="Search technologies..."
      ={}
      ={250}
      ={(value) => {
value: "react" | "vue" | "svelte" | "nodejs" | "python" | "go"
.(); }} /> ); }

Disabled Options

disabled-options.tsx
import {  } from "@/components/typed-combobox";

export function () {
  return (
    <
      ="Select a plan..."
      ="Search plans..."
      ={
        [
          { : "Free", : "free" },
          { : "Pro", : "pro" },
          { : "Enterprise", : "enterprise", : true },
        ] as 
      }
    />
  );
}

Controlled Component

controlled.tsx
"use client";

import {  } from "react";
import {  } from "@/components/typed-combobox";

const  = [
  { : "Next.js", : "nextjs" },
  { : "Remix", : "remix" },
  { : "Astro", : "astro" },
  { : "SvelteKit", : "sveltekit" },
] as ;

type  = (typeof )[number]["value"];

export function () {
  const [, ] = <>("nextjs");

  return (
    < ="space-y-4">
      <
        ="Select a framework..."
        ="Search..."
        ={}
        ={}
        ={}
      />
      < ="text-sm text-muted-foreground">
        Selected: < ="font-mono">{}</>
      </>
    </>
  );
}

Selected: nextjs

Custom Filter Function

You can customize the search behavior using the filterFn prop to search across multiple fields:

custom-filter.tsx
import {  } from "@/components/typed-combobox";

const  = [
  { : 1, : "John Doe", : "john@example.com", : "admin" },
  { : 2, : "Jane Smith", : "jane@example.com", : "user" },
  { : 3, : "Bob Johnson", : "bob@example.com", : "admin" },
  { : 4, : "Alice Brown", : "alice@example.com", : "user" },
] as ;

export function () {
  return (
    <
      ="Search by name, email, or role..."
      ="Type to search..."
      ={}
      ="id"
      ="name"
      ={300}
      ={(, ) => {
        const  = .();
        return (
          ..().() ||
          ..().() ||
          ..().()
        );
      }}
      ={() => {
        .();
      }}
    />
  );
}

Custom Keys (id/name)

By default, TypedCombobox uses value and label keys. You can customize this using valueKey and labelKey props:

custom-keys.tsx
import {  } from "@/components/typed-combobox";

const  = [
  { : 1, : "John Doe", : "john@example.com" },
  { : 2, : "Jane Smith", : "jane@example.com" },
  { : 3, : "Bob Johnson", : "bob@example.com" },
] as ;

export function () {
  return (
    <
      ="Select a user..."
      ="Search users..."
      ={}
      ="id"
      ="name"
      ={250}
      ={(value) => {
value: 1 | 2 | 3
.(); }} /> ); }

API Reference

TypedComboboxProps

PropTypeDefaultDescription
optionsarray-Array of options or grouped options
valueKey?TValueKey"value"The key to use for the option value
labelKey?TLabelKey"label"The key to use for the option label
value?ExtractOptionValue<TOptions, TValueKey>-Controlled value
defaultValue?ExtractOptionValue<TOptions, TValueKey>-Default value for uncontrolled usage
onValueChange?function-Callback when value changes
placeholder?string"Select an option..."Placeholder for trigger button
searchPlaceholder?string"Search..."Placeholder for search input
emptyText?string"No results found."Text when no options match
filterFn?function-Custom filter function for search
popoverWidth?string | number200Width of the popover content
triggerClassName?string-Class name for the trigger button
contentClassName?string-Class name for the popover content
renderTrigger?function-Custom render for trigger
renderItem?function-Custom render for each item
renderGroup?function-Custom render for each group
renderEmpty?function-Custom render for empty state
renderContent?function-Custom render for content wrapper
disabled?booleanfalseWhether the combobox is disabled
open?boolean-Controlled open state
onOpenChange?function-Callback when open state changes

ComboboxOption

PropTypeDescription
valueTThe option value
labelstringThe display label
disabled?booleanWhether the option is disabled

ComboboxOptionGroup

PropTypeDescription
labelstringThe group label
optionsarrayArray of options in this group

ComboboxTriggerRenderProps

PropTypeDescription
selectedOptionTOption | undefinedCurrently selected option
placeholderstringPlaceholder text
openbooleanWhether the combobox is open
disabledbooleanWhether the combobox is disabled
ButtoncomponentDefault Button component

ComboboxItemRenderProps

PropTypeDescription
optionTOptionThe option data
isSelectedbooleanWhether this option is selected
isDisabledbooleanWhether this option is disabled
groupComboboxOptionGroup<TOption> | undefinedGroup this option belongs to
itemPropsobjectProps to spread on CommandItem
ItemcomponentDefault CommandItem component

ComboboxGroupRenderProps

PropTypeDescription
groupComboboxOptionGroup<TOption>The group data
childrenReactNodeRendered items inside this group
GroupcomponentDefault CommandGroup component

ComboboxEmptyRenderProps

PropTypeDescription
searchQuerystringThe current search query
EmptycomponentDefault CommandEmpty component

ComboboxContentRenderProps

PropTypeDescription
childrenReactNodeRendered items/groups
CommandcomponentCommand component
CommandInputcomponentCommandInput component
CommandListcomponentCommandList component
searchQuerystringCurrent search query
setSearchQueryfunctionUpdate search query
searchPlaceholderstringSearch input placeholder
emptyStateReactNodeEmpty state component

On this page