rapidUI

Native Select

A styled native HTML select element for simple lists and mobile-friendly forms.

Installation

pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/native-select.json

Installs to @/components/ui/native-select.

Manual installation

Install the dependencies:

pnpm add lucide-react@^1.48.0

Copy the source into your project:

components/ui/native-select.tsx
import { ChevronDownIcon } from 'lucide-react'
import type * as React from 'react'
import { cn } from '@/lib/utils'

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-10 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors 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/50 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 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
				{...props}
			/>
			<ChevronDownIcon
				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 }

A styled native HTML select element. Use it when you want the platform picker (for example on mobile devices), native form submission or maximum robustness, instead of the custom Select popover.

Features

  • Native select behaviour and form submission
  • Sizes: default and sm
  • Option groups
  • Disabled and invalid states
  • Consistent styling with other inputs

Usage

import { NativeSelect, NativeSelectOption } from '@/components/ui/native-select'

export function CaseTypeSelect() {
  return (
    <NativeSelect defaultValue="">
      <NativeSelectOption value="" disabled>
        Select case type
      </NativeSelectOption>
      <NativeSelectOption value="building-permit">Building permit</NativeSelectOption>
      <NativeSelectOption value="parking-permit">Parking permit</NativeSelectOption>
    </NativeSelect>
  )
}

Examples

Default

A case type picker with a label and a description.

With Groups

Departments organised in option groups.

Sizes

The default and sm sizes.

Filter Toolbar

Small selects used as filters above a case list. Each select has a visually hidden label.

In a Form

A school placement application with several selects in a card.

Disabled

A locked case status.

Invalid

A required parking zone with an error message.

Accessibility

  • Uses the native select element with full platform accessibility
  • Associate a label with htmlFor and id; use a visually hidden label when the context makes it obvious
  • Set aria-invalid for invalid values and describe the error in text
  • The chevron icon is aria-hidden