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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
options | { label: string; value: string }[] | None | Required: the options |
placeholder | string | "Select..." | Empty-state label |
size | "sm" | "md" | "lg" | "md" | Height + padding |
invalid | boolean | false | Error state |