Native Select
Native select example: a styled browser select element with a caret icon, small and default sizes, and option and optgroup parts. No custom popup, so platform pickers work.
pnpm dlx shadcn add @01orbital/native-select"use client"
import * as React from "react"
import { CaretDownIcon } from "@phosphor-icons/react"
import { cn } from "cn"
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
size?: "sm" | "default"
}
function NativeSelect({
className,
size = "default",
...props
}: NativeSelectProps) {
return (
<div
className={cn(
"group/native-select relative w-fit has-[select:disabled]:opacity-50",
className
)}
data-slot="native-select-wrapper"
data-size={size}
>
<select
data-slot="native-select"
data-size={size}
className="h-9 w-full min-w-0 appearance-none rounded-3xl border border-transparent bg-input/50 py-1 pr-8 pl-3 text-sm transition-[color,box-shadow,background-color] outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-8 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
{...props}
/>
<CaretDownIcon
className="pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted-foreground select-none"
aria-hidden="true"
data-slot="native-select-icon"
/>
</div>
)
}
function NativeSelectOption({
className,
...props
}: React.ComponentProps<"option">) {
return (
<option
data-slot="native-select-option"
className={cn("bg-[Canvas] text-[CanvasText]", className)}
{...props}
/>
)
}
function NativeSelectOptGroup({
className,
...props
}: React.ComponentProps<"optgroup">) {
return (
<optgroup
data-slot="native-select-optgroup"
className={cn("bg-[Canvas] text-[CanvasText]", className)}
{...props}
/>
)
}
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }
import {
NativeSelect,
NativeSelectOption,
} from "@/components/ui/native-select"
// The copy: change text here, not in the markup.
const COPY = {
placeholder: "Select status",
} as const
const STATUSES = [
{ value: "todo", label: "Todo" },
{ value: "in-progress", label: "In Progress" },
{ value: "done", label: "Done" },
{ value: "canceled", label: "Canceled" },
] as const
export default function NativeSelectDemo() {
return (
<NativeSelect>
<NativeSelectOption value="">{COPY.placeholder}</NativeSelectOption>
{STATUSES.map((status) => (
<NativeSelectOption key={status.value} value={status.value}>
{status.label}
</NativeSelectOption>
))}
</NativeSelect>
)
}
Dependencies
npm: @phosphor-icons/react, cn. The CLI installs them for you.
No other registry pieces.