GitHub

Select

Styled native select for predictable keyboard behavior and minimal setup.

01 Live Demo

expand_more

02 Code Snippet

src/ui/Select.tsx
import { Select } from "@/ui/Select";

<Select.Root
  label="Framework"
  value={framework}
  onChange={(event) => setFramework(event.target.value)}
  options={[
    { label: "Next.js", value: "next" },
    { label: "Vite", value: "vite" },
    { label: "Remix", value: "remix" },
  ]}
/>

03 Copy-Paste (Single File)

Select.tsx
import { forwardRef, type SelectHTMLAttributes } from "react";

type SelectProps = SelectHTMLAttributes<HTMLSelectElement> & {
  label?: string;
  options?: Array<{ label: string; value: string }>;
};

const SelectRoot = forwardRef<HTMLSelectElement, SelectProps>(function SelectRoot(
  { label, options = [], children, className, ...props },
  ref
) {
  return (
    <div className={className}>
      {label && <label className="mb-1.5 block text-sm font-medium text-slate-700 dark:text-slate-300">{label}</label>}
      <div className="relative">
        <select
          ref={ref}
          className="h-10 w-full appearance-none rounded-lg border border-slate-200 bg-white px-3.5 pr-10 text-sm text-slate-900 outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20 dark:border-[#1f2937] dark:bg-[#0d1117] dark:text-white"
          {...props}
        >
          {options.map((option) => (
            <option key={option.value} value={option.value}>{option.label}</option>
          ))}
          {children}
        </select>
        <span className="material-symbols-outlined pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[18px] text-slate-400">expand_more</span>
      </div>
    </div>
  );
});

type SelectCompound = typeof SelectRoot & { Root: typeof SelectRoot };
export const Select = Object.assign(SelectRoot, { Root: SelectRoot }) as SelectCompound;
react-principles