Components
Combobox
A searchable select with text filtering, wrapping Morphos's Combobox primitive.
Installation
npx kosmesis add comboboxpnpm dlx kosmesis add comboboxyarn dlx kosmesis add comboboxbunx kosmesis add comboboxInstall the following dependencies:
npm install @morphos/inputspnpm add @morphos/inputsyarn add @morphos/inputsbun add @morphos/inputsCopy and paste the following code into your project.
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/csspnpm add @morphos/inputs @praxisjs/cssyarn add @morphos/inputs @praxisjs/cssbun add @morphos/inputs @praxisjs/cssCopy and paste the following code into your project.
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
| Prop | Type | Default |
|---|---|---|
options | ComboboxOption[] ({ value: string; label: string; disabled?: boolean }) | — required |
value | string | — |
defaultValue | string | — |
placeholder | string | — |
disabled | boolean | — |
required | boolean | — |
name | string | — |
filterFn | (option: ComboboxOption, query: string) => boolean | — |
onValueChange | (value: string) => void | — |
onQueryChange | (query: string) => void | — |
aria-label | string | — |
aria-labelledby | string | — |
aria-describedby | string | — |