Kosmesis
Components

Combobox

A searchable select with text filtering, wrapping Morphos's Combobox primitive.

Installation

npx kosmesis add combobox
pnpm dlx kosmesis add combobox
yarn dlx kosmesis add combobox
bunx kosmesis add combobox

Install the following dependencies:

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Copy and paste the following code into your project.

combobox.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Combobox as MorphosCombobox, type ComboboxProps as MorphosComboboxProps  } from "@morphos/inputs";import { cn } from "@/lib/utils";export type ComboboxProps = MorphosComboboxProps;// For multi-section, keyboard-first "type to jump anywhere" UI, see `./command.tsx`, which// composes this same primitive with `Dialog`.@Component()export class Combobox extends StatelessComponent<ComboboxProps> {  render() {    const { class: cls, ...rest } = this.props;    return (      <MorphosCombobox        class={cn(          "relative",          "[&>input]:flex [&>input]:h-9 [&>input]:w-full [&>input]:rounded-md [&>input]:border [&>input]:border-input [&>input]:bg-transparent [&>input]:px-3 [&>input]:py-1 [&>input]:text-sm [&>input]:shadow-xs [&>input]:outline-none [&>input]:placeholder:text-muted-foreground",          "[&>input:disabled]:cursor-not-allowed [&>input:disabled]:opacity-50",          "data-open:[&>input]:rounded-b-none",          "[&>ul]:absolute [&>ul]:z-50 [&>ul]:mt-1 [&>ul]:max-h-60 [&>ul]:w-full [&>ul]:overflow-auto [&>ul]:rounded-md [&>ul]:border [&>ul]:bg-popover [&>ul]:p-1 [&>ul]:text-popover-foreground [&>ul]:shadow-md",          "[&_li]:relative [&_li]:flex [&_li]:cursor-default [&_li]:items-center [&_li]:rounded-sm [&_li]:px-2 [&_li]:py-1.5 [&_li]:text-sm [&_li]:outline-none",          // `data-active` only comes from keyboard nav — hover needs its own `:hover` rule.          "[&_li:hover]:bg-accent [&_li:hover]:text-accent-foreground",          "[&_li[data-active]]:bg-accent [&_li[data-active]]:text-accent-foreground",          "[&_li[data-disabled]]:pointer-events-none [&_li[data-disabled]]:opacity-50",          cls,        )}        {...rest}      />    );  }}

Install the following dependencies:

npm install @morphos/inputs @praxisjs/css
pnpm add @morphos/inputs @praxisjs/css
yarn add @morphos/inputs @praxisjs/css
bun add @morphos/inputs @praxisjs/css

Copy and paste the following code into your project.

combobox.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Combobox as MorphosCombobox, type ComboboxProps as MorphosComboboxProps  } from "@morphos/inputs";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class ComboboxStyles extends Stylesheet {  $root = this.css({ position: "relative" })    .on("& > input", {      display: "flex",      height: "2.25rem",      width: "100%",      borderRadius: `calc(${t.radius} - 2px)`,      border: `1px solid ${t.input}`,      backgroundColor: "transparent",      padding: "0.25rem 0.75rem",      fontSize: "0.875rem",      boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",      outline: "none",    })    .on("& > input::placeholder", { color: t.mutedForeground })    .on("& > input:disabled", { cursor: "not-allowed", opacity: 0.5 })    .on('&[data-open] > input', { borderBottomLeftRadius: "0", borderBottomRightRadius: "0" })    .on("& > ul", {      position: "absolute",      zIndex: 50,      marginTop: "0.25rem",      maxHeight: "15rem",      width: "100%",      overflow: "auto",      borderRadius: `calc(${t.radius} - 2px)`,      border: `1px solid ${t.border}`,      backgroundColor: t.popover,      padding: "0.25rem",      color: t.popoverForeground,      boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1)",    })    .on("& li", {      position: "relative",      display: "flex",      cursor: "default",      alignItems: "center",      borderRadius: "0.125rem",      padding: "0.375rem 0.5rem",      fontSize: "0.875rem",      outline: "none",    })    // `data-active` only comes from keyboard nav — hover needs its own `:hover` rule.    .on("& li:hover", { backgroundColor: t.accent, color: t.accentForeground })    .on("& li[data-active]", { backgroundColor: t.accent, color: t.accentForeground })    .on("& li[data-disabled]", { pointerEvents: "none", opacity: 0.5 });}export type ComboboxProps = MorphosComboboxProps;// For multi-section, keyboard-first "type to jump anywhere" UI, see `./command.tsx`, which// composes this same primitive with `Dialog`.@Component()export class Combobox extends StatelessComponent<ComboboxProps> {  @Styled(ComboboxStyles) $s!: ComboboxStyles;  render() {    const { class: cls, ...rest } = this.props;    return <MorphosCombobox class={cx(this.$s.$root, cls)} {...rest} />;  }}

Examples

Usage

import { Combobox } from "@/components/ui/combobox";

<Combobox
  placeholder="Search frameworks..."
  options={[
    { value: "praxisjs", label: "PraxisJS" },
    { value: "react", label: "React" },
    { value: "vue", label: "Vue" },
  ]}
  onValueChange={(value) => console.log(value)}
/>

Need a full "type to jump anywhere" command palette (grouped items, a ⌘K dialog trigger)? See Command, which is built for that instead of Combobox's single-field-select use case.

Props

PropTypeDefault
optionsComboboxOption[] ({ value: string; label: string; disabled?: boolean })— required
valuestring
defaultValuestring
placeholderstring
disabledboolean
requiredboolean
namestring
filterFn(option: ComboboxOption, query: string) => boolean
onValueChange(value: string) => void
onQueryChange(query: string) => void
aria-labelstring
aria-labelledbystring
aria-describedbystring

On this page