Skip to content

InputsSelect

Select

The native select element, styled to match the rest of the input system. The chevron is decorative; the OS-native affordance is preserved.

Native <select>3 sizesInvalid state

Demo

The chevron is a pseudo-element overlay; the native select element is still the actual control.

Default

Sizes

Sizes

Implementation

select.tsx
import { forwardRef, type SelectHTMLAttributes } from "react"
import { ChevronDown } from "lucide-react"
import SiteIcon from "@/components/site-icon"

interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
  options: { label: string; value: string }[]
  size?: "sm" | "md" | "lg"
  invalid?: boolean
}

export const Select = forwardRef<HTMLSelectElement, SelectProps>(
  function Select({ options, size = "md", invalid, className, ...props }, ref) {
    return (
      <div className="ds-select-wrap">
        <select
          ref={ref}
          className={cn("ds-select", `ds-select--${size}`, invalid && "ds-select--invalid", className)}
          aria-invalid={invalid || undefined}
          {...props}
        >
          {options.map((opt) => (
            <option key={opt.value} value={opt.value}>{opt.label}</option>
          ))}
        </select>
        <SiteIcon icon={ChevronDown} size="sm" className="ds-select__chevron" tone="muted" />
      </div>
    )
  },
)

Props

PropTypeDefaultDescription
options{ label: string; value: string }[]NoneRequired: the options
placeholderstring"Select..."Empty-state label
size"sm" | "md" | "lg""md"Height + padding
invalidbooleanfalseError state