Native Select

A hand-drawn native select styled with shadcn conventions. It is a real <select>, so it gets the platform dropdown on mobile and needs no popover.

Native Select
Every hand-drawn native select size and state.
Sizes
Grouped
Invalid
Disabled

Installation

npx shadcn@latest add https://sketchcn.chuwii.com/r/native-select.json

Usage

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

export function PenPicker() {
  return (
    <NativeSelect defaultValue="marker" className="w-40">
      <NativeSelectOption value="ballpoint">Ballpoint</NativeSelectOption>
      <NativeSelectOption value="marker">Marker</NativeSelectOption>
      <NativeSelectOption value="pencil">Pencil</NativeSelectOption>
    </NativeSelect>
  );
}

Parts

PartDescription
NativeSelectThe outlined select with a drawn chevron.
NativeSelectOptionA styled option.
NativeSelectOptGroupA styled optgroup.

Props

PropTypeDefaultDescription
size"sm" | "default""default"Height and text size. Note this replaces the native size attribute.

Every other prop is forwarded to the native select and behaves exactly as usual.

Examples

Form field

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

const PAPERS = {
  graph: "Graph paper",
  dotted: "Dotted",
  plain: "Plain",
};

export function PaperField() {
  const id = useId();

  return (
    <div className="flex flex-col gap-2">
      <label htmlFor={id} className="text-sm">
        Paper
      </label>
      <NativeSelect id={id} name="paper" className="w-52">
        {Object.entries(PAPERS).map(([value, label]) => (
          <NativeSelectOption key={value} value={value}>
            {label}
          </NativeSelectOption>
        ))}
      </NativeSelect>
    </div>
  );
}

Grouped

Drawing with marker
import {
  NativeSelect,
  NativeSelectOptGroup,
  NativeSelectOption,
} from "@/components/ui/native-select";

export function StrokePicker() {
  const [stroke, setStroke] = useState("marker");

  return (
    <div className="flex items-center gap-3">
      <NativeSelect
        className="w-52"
        value={stroke}
        onChange={(event) => setStroke(event.target.value)}
      >
        <NativeSelectOptGroup label="Wet ink">
          <NativeSelectOption value="marker">Marker</NativeSelectOption>
          <NativeSelectOption value="fineliner">Fineliner</NativeSelectOption>
        </NativeSelectOptGroup>
        <NativeSelectOptGroup label="Dry">
          <NativeSelectOption value="pencil">Pencil</NativeSelectOption>
          <NativeSelectOption value="charcoal">Charcoal</NativeSelectOption>
        </NativeSelectOptGroup>
      </NativeSelect>
      <span className="text-sm">Drawing with {stroke}</span>
    </div>
  );
}