Skip to content
Dethink Components

ComponentsSelect

Select

Let users choose one option from a dropdown list.

On this page

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
import { Select, SelectItem } from "@dethink/components";

export function Example() {
  return (
    <Select label="Region" name="region" placeholder="Choose a region">
      <SelectItem value="us">United States</SelectItem>
      <SelectItem value="uk">United Kingdom</SelectItem>
    </Select>
  );
}

Try the examples, then open the code to use them in your app. Open with Space or Enter, jump options by typing, and confirm with Enter.

Basic

Label, placeholder, helper description, and a name for native form submission.

RegionData is stored in the selected region.
Show sourceexamples/select/basic.tsx
examples/select/basic.tsx
"use client";

import { Select, SelectItem } from "@dethink/components";

export function SelectBasic() {
  return (
    <div className="mx-auto max-w-xs">
      <Select
        label="Region"
        placeholder="Choose a region"
        description="Data is stored in the selected region."
        name="region"
      >
        <SelectItem value="us-east">US East (N. Virginia)</SelectItem>
        <SelectItem value="eu-west">EU West (Ireland)</SelectItem>
        <SelectItem value="ap-south">AP South (Mumbai)</SelectItem>
      </Select>
    </div>
  );
}

Dynamic and rich options

Pass items to build options from data. Add textValue when an option contains icons or extra text, so users can still find it by typing.

Model
Show sourceexamples/select/options.tsx
examples/select/options.tsx
"use client";

import { Select, SelectItem } from "@dethink/components";

const models = [
  { value: "fast", label: "Fast", note: "Lowest latency" },
  { value: "balanced", label: "Balanced", note: "Default quality" },
  { value: "reasoning", label: "Reasoning", note: "Deep analysis" },
  { value: "legacy", label: "Legacy", note: "Deprecated" },
];

export function SelectOptions() {
  return (
    <div className="mx-auto max-w-xs">
      <Select
        label="Model"
        defaultValue="balanced"
        items={models}
        disabledKeys={["legacy"]}
      >
        {(item) => (
          <SelectItem
            key={item.value}
            value={item.value}
            textValue={item.label}
          >
            <span className="flex w-full items-baseline justify-between gap-3">
              {item.label}
              <span className="text-muted-foreground text-xs">{item.note}</span>
            </span>
          </SelectItem>
        )}
      </Select>
    </div>
  );
}

Sizes

controlSize aligns the trigger with Input and Button heights.

Size sm
Size md
Size lg
Show sourceexamples/select/sizes.tsx
examples/select/sizes.tsx
"use client";

import { Select, SelectItem } from "@dethink/components";

const sizes = ["sm", "md", "lg"] as const;

export function SelectSizes() {
  return (
    <div className="mx-auto grid max-w-md gap-4">
      {sizes.map((size) => (
        <Select
          key={size}
          controlSize={size}
          label={`Size ${size}`}
          defaultValue="staging"
        >
          <SelectItem value="production">Production</SelectItem>
          <SelectItem value="staging">Staging</SelectItem>
          <SelectItem value="sandbox">Sandbox</SelectItem>
        </Select>
      ))}
    </div>
  );
}

Form states

Required, disabled, read-only, and invalid with an error message.

Required
Disabled
Read-only
InvalidYour card was declined for this plan.
Show sourceexamples/select/states.tsx
examples/select/states.tsx
"use client";

import { Select, SelectItem } from "@dethink/components";

const plans = [
  { value: "starter", label: "Starter" },
  { value: "team", label: "Team" },
  { value: "scale", label: "Scale" },
];

export function SelectStates() {
  return (
    <div className="mx-auto grid max-w-md gap-5 sm:grid-cols-2">
      <Select label="Required" required placeholder="Pick a plan" items={plans}>
        {(item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        )}
      </Select>
      <Select label="Disabled" disabled defaultValue="team" items={plans}>
        {(item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        )}
      </Select>
      <Select label="Read-only" readOnly defaultValue="scale" items={plans}>
        {(item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        )}
      </Select>
      <Select
        label="Invalid"
        invalid
        errorMessage="Your card was declined for this plan."
        defaultValue="scale"
        items={plans}
      >
        {(item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        )}
      </Select>
    </div>
  );
}

Examples that combine components for common tasks.

Live density switcher

Let users choose how much space controls use. Changing the selection updates the surrounding layout.

Interface density

Invite teammate

Everything below respacing is driven by one attribute.

Show sourceexamples/select/recipe-density.tsx
examples/select/recipe-density.tsx
"use client";

import { useState } from "react";
import {
  Button,
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
  Input,
  Select,
  SelectItem,
} from "@dethink/components";

/**
 * The library's density contract is a plain `data-density` attribute, so a
 * Select can retheme an entire subtree live — no component changes needed.
 */
export function SelectRecipeDensity() {
  const [density, setDensity] = useState("default");

  return (
    <div className="space-y-5">
      <div className="mx-auto max-w-xs">
        <Select
          label="Interface density"
          value={density}
          onValueChange={(value) => setDensity(value)}
        >
          <SelectItem value="compact">Compact</SelectItem>
          <SelectItem value="default">Default</SelectItem>
          <SelectItem value="comfortable">Comfortable</SelectItem>
        </Select>
      </div>
      <div data-density={density}>
        <Card className="mx-auto max-w-sm">
          <CardHeader>
            <CardTitle>Invite teammate</CardTitle>
            <CardDescription>
              Everything below respacing is driven by one attribute.
            </CardDescription>
          </CardHeader>
          <CardContent className="space-y-3">
            <div className="space-y-1.5">
              <label
                htmlFor="density-invite-email"
                className="text-sm font-medium"
              >
                Email
              </label>
              <Input
                id="density-invite-email"
                placeholder="teammate@company.com"
              />
            </div>
          </CardContent>
          <CardFooter justify="end">
            <Button variant="ghost">Cancel</Button>
            <Button>Send invite</Button>
          </CardFooter>
        </Card>
      </div>
    </div>
  );
}

Use the label prop to name the field. Select connects the label, help text, and error message for you.

Select props
PropWhat it doesDefault
valuestringThe selected value. Use with onValueChange when your app manages the selection.Not set
defaultValuestringThe starting selection when the component manages its own state.Not set
onValueChange(value: string) => voidCalled with the value the user selects.Not set
childrenSelectItem nodes | (item) => SelectItemAdd SelectItem elements, or a function that renders each entry in items.Not set
itemsIterable<SelectItemData>A list of options. Each needs a value; use the children function to render it.Not set
disabledKeysIterable<string>The values of options users cannot select.Not set
label / description / errorMessageReactNodeThe field label, help text, and error message.Not set
placeholderstringText shown in the trigger before a selection exists.Not set
controlSize"sm" | "md" | "lg"Sets the button height and text size."md"
required / disabled / readOnly / invalidbooleanMakes the field required, disabled, read-only, or invalid.false
open / defaultOpen / onOpenChangeboolean / boolean / (open) => voidUse open with onOpenChange to manage the list, or defaultOpen to set its starting state.Not set
namestringThe field name used when submitting a form.Not set
SelectItem props
PropWhat it doesDefault
valuestringA unique value for this option. Returned by onValueChange and submitted with the form.Not set
textValuestringText used to find this option when the user types. Set it when the option includes more than plain text.text content
disabledbooleanDisables this option only.false