Kosmesis
Components

Select

A custom listbox select with keyboard support, wrapping Morphos's Select primitive.

Installation

npx kosmesis add select
pnpm dlx kosmesis add select
yarn dlx kosmesis add select
bunx kosmesis add select

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.

select.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Select as MorphosSelect, type SelectProps as MorphosSelectProps  } from "@morphos/inputs";import { cn } from "@/lib/utils";export type SelectProps = MorphosSelectProps;/** * Morphos's `Select` takes a flat `options` array and renders its own trigger + listbox as one * unit — there's no `SelectTrigger`/`SelectContent`/`SelectItem` compound API to wrap (unlike * shadcn/ui's Radix-backed version). If you need children-based composition, see `Combobox` for * search-filtered lists, or `NativeSelect` for a plain `<select>`. */@Component()export class Select extends StatelessComponent<SelectProps> {  render() {    const { class: cls, ...rest } = this.props;    return (      <MorphosSelect        class={cn(          "relative",          "[&>button]:flex [&>button]:h-9 [&>button]:w-full [&>button]:items-center [&>button]:justify-between [&>button]:gap-2 [&>button]:rounded-md [&>button]:border [&>button]:border-input [&>button]:bg-transparent [&>button]:px-3 [&>button]:py-2 [&>button]:text-sm [&>button]:whitespace-nowrap [&>button]:shadow-xs [&>button]:outline-none",          "[&>button[data-placeholder]]:text-muted-foreground",          "[&>button:disabled]:cursor-not-allowed [&>button:disabled]:opacity-50",          "[&>button[aria-expanded=true]]:border-ring [&>button[aria-expanded=true]]:ring-[3px] [&>button[aria-expanded=true]]:ring-ring/50",          "[&>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",          "[&_li[data-active]]:bg-accent [&_li[data-active]]:text-accent-foreground",          "[&_li[data-selected]]:font-medium",          "[&_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.

select.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Select as MorphosSelect, type SelectProps as MorphosSelectProps  } from "@morphos/inputs";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class SelectStyles extends Stylesheet {  $root = this.css({ position: "relative" })    .on("& > button", {      display: "flex",      height: "2.25rem",      width: "100%",      alignItems: "center",      justifyContent: "space-between",      gap: "0.5rem",      borderRadius: `calc(${t.radius} - 2px)`,      border: `1px solid ${t.input}`,      backgroundColor: "transparent",      padding: "0.5rem 0.75rem",      fontSize: "0.875rem",      whiteSpace: "nowrap",      boxShadow: "0 1px 2px 0 rgb(0 0 0 / 0.05)",      outline: "none",    })    .on("& > button[data-placeholder]", { color: t.mutedForeground })    .on("& > button:disabled", { cursor: "not-allowed", opacity: 0.5 })    .on('& > button[aria-expanded="true"]', {      borderColor: t.ring,      boxShadow: `0 0 0 3px color-mix(in oklab, ${t.ring} 50%, transparent)`,    })    .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",    })    .on("& li[data-active]", { backgroundColor: t.accent, color: t.accentForeground })    .on("& li[data-selected]", { fontWeight: 500 })    .on("& li[data-disabled]", { pointerEvents: "none", opacity: 0.5 });}export type SelectProps = MorphosSelectProps;/** * Morphos's `Select` takes a flat `options` array and renders its own trigger + listbox as one * unit — there's no `SelectTrigger`/`SelectContent`/`SelectItem` compound API to wrap (unlike * shadcn/ui's Radix-backed version). If you need children-based composition, see `Combobox` for * search-filtered lists, or `NativeSelect` for a plain `<select>`. */@Component()export class Select extends StatelessComponent<SelectProps> {  @Styled(SelectStyles) $s!: SelectStyles;  render() {    const { class: cls, ...rest } = this.props;    return <MorphosSelect class={cx(this.$s.$root, cls)} {...rest} />;  }}

Examples

Usage

import { Select } from "@/components/ui/select";

<Select
  placeholder="Select a fruit"
  options={[
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "cherry", label: "Cherry" },
  ]}
  onValueChange={(value) => console.log(value)}
/>

Morphos's Select takes a flat options array and renders its own trigger + listbox as one unit — there's no SelectTrigger/SelectContent/SelectItem compound API to compose (Radix has one; Morphos deliberately doesn't). Need children-based composition or free-text search instead? See Combobox. Need a plain native picker? See Native Select.

Props

PropType
options{ value: string; label: string; disabled?: boolean }[]
value / defaultValuestring
placeholderstring
disabled / required / clearableboolean
onValueChange(value: string) => void
onClear() => void

On this page