A styled native HTML select element with consistent design system integration.
1import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";23export default function Preview() {4 return (Installation
pnpm dlx sprawlify@latest add native-selectnpx sprawlify@latest add native-selectyarn sprawlify@latest add native-selectbunx --bun sprawlify@latest add native-select
Install the following dependencies:
pnpm add @sprawlify/primitives @sprawlify/reactnpm install @sprawlify/primitives @sprawlify/reactyarn add @sprawlify/primitives @sprawlify/reactbun add @sprawlify/primitives @sprawlify/react
Add the following files to your project:
1"use client";23import * as React from "react";4import { cn } from "@/lib/utils";5import { ChevronDownIcon } from "lucide-react";67type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {8 size?: "sm" | "default";9};1011function NativeSelect({ className, size = "default", ...props }: NativeSelectProps) {12 return (13 <div14 className={cn(15 "group/native-select relative w-fit has-[select:disabled]:opacity-50",16 className,17 )}18 data-scope="native-select"19 data-part="root"20 data-slot="native-select-root"21 data-size={size}22 >23 <select24 data-slot="native-select"25 data-size={size}26 className="border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 w-full min-w-0 appearance-none rounded-lg border bg-input/30 py-1 pr-8 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 outline-none disabled:pointer-events-none disabled:cursor-not-allowed"27 {...props}28 />29 <ChevronDownIcon30 className="text-muted-foreground top-1/2 right-2.5 size-4 -translate-y-1/2 pointer-events-none absolute select-none"31 aria-hidden="true"32 data-slot="native-select-icon"33 />34 </div>35 );36}3738function NativeSelectOption({ ...props }: React.ComponentProps<"option">) {39 return (40 <option41 data-scope="native-select"42 data-part="option"43 data-slot="native-select-option"44 {...props}45 />46 );47}4849function NativeSelectOptGroup({ className, ...props }: React.ComponentProps<"optgroup">) {50 return (51 <optgroup52 data-scope="native-select"53 data-part="optgroup"54 data-slot="native-select-optgroup"55 className={cn(className)}56 {...props}57 />58 );59}6061export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };62Update the import paths to match your project setup.
Usage
1import {
2 NativeSelect,
3 NativeSelectOptGroup,
4 NativeSelectOption
5} from "@/components/ui/native-select"1<NativeSelect>
2 <NativeSelectOption value="">Select a fruit</NativeSelectOption>
3 <NativeSelectOption value="apple">Apple</NativeSelectOption>
4 <NativeSelectOption value="banana">Banana</NativeSelectOption>
5 <NativeSelectOption value="blueberry">Blueberry</NativeSelectOption>
6 <NativeSelectOption value="pineapple">Pineapple</NativeSelectOption>
7</NativeSelect>Examples
Disabled
Add the disabled prop to the NativeSelect component to disable the select.
1import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";23export default function Preview() {4 return (Groups
Use NativeSelectOptGroup to organize options into categories.
1import {2 NativeSelect,3 NativeSelectOptGroup,4 NativeSelectOption,Invalid
Use aria-invalid to show validation errors and the data-invalid attribute to the Field component for styling.
1import { NativeSelect, NativeSelectOption } from "@/components/ui/native-select";23export default function Preview() {4 return (