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;/** * Morphos's `Combobox` takes a flat `options` array rather than a `CommandItem`-style children * composition — it renders the input, listbox, and filtered options itself. If you need * multi-section, keyboard-first "type to jump anywhere" UI (like shadcn/ui's `Command`), 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",          // Morphos's `Combobox` only sets `data-active` from keyboard navigation (arrow keys) —          // there's no `onMouseEnter` wiring, so mouse hover needs its own plain `:hover` rule to          // get any visual feedback at all.          "[&_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",    })    // Morphos's `Combobox` only sets `data-active` from keyboard navigation (arrow keys) — there's    // no `onMouseEnter` wiring, so mouse hover needs its own plain `:hover` rule to get any visual    // feedback at all.    .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;/** * Morphos's `Combobox` takes a flat `options` array rather than a `CommandItem`-style children * composition — it renders the input, listbox, and filtered options itself. If you need * multi-section, keyboard-first "type to jump anywhere" UI (like shadcn/ui's `Command`), 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