Skip to main content

Overview

This guide will help you create your first wheel picker component. We’ll cover both controlled and uncontrolled patterns, and show you how to build common use cases like time pickers and selection menus.
Make sure you’ve completed the installation before proceeding.

Basic Usage

Simple Controlled Picker

The most common pattern is a controlled component where you manage the selected value in state:
import { useState } from "react";
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

const frameworks: WheelPickerOption[] = [
  { label: "Next.js", value: "nextjs" },
  { label: "Vite", value: "vite" },
  { label: "Remix", value: "remix" },
  { label: "Astro", value: "astro" },
  { label: "Gatsby", value: "gatsby" },
];

export function FrameworkPicker() {
  const [value, setValue] = useState("nextjs");

  return (
    <div>
      <p>Selected framework: {value}</p>
      <WheelPickerWrapper>
        <WheelPicker
          options={frameworks}
          value={value}
          onValueChange={setValue}
        />
      </WheelPickerWrapper>
    </div>
  );
}

Uncontrolled Picker

For simpler use cases, you can use an uncontrolled component with defaultValue:
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

const sizes: WheelPickerOption[] = [
  { label: "Small", value: "sm" },
  { label: "Medium", value: "md" },
  { label: "Large", value: "lg" },
  { label: "Extra Large", value: "xl" },
];

export function SizePicker() {
  const handleChange = (value: string) => {
    console.log("Selected size:", value);
  };

  return (
    <WheelPickerWrapper>
      <WheelPicker
        options={sizes}
        defaultValue="md"
        onValueChange={handleChange}
      />
    </WheelPickerWrapper>
  );
}

Component Structure

1

WheelPickerWrapper

The wrapper component provides the container structure:
<WheelPickerWrapper className="w-56 rounded-md border">
  {/* Place one or more WheelPicker components here */}
</WheelPickerWrapper>
2

WheelPicker

The picker component renders a single scrollable wheel:
<WheelPicker
  options={options}
  value={value}
  onValueChange={setValue}
/>
3

Options Array

Define your options with labels and values:
const options: WheelPickerOption[] = [
  { label: "Display Text", value: "internal-value" },
];

Common Patterns

Multiple Wheels (Time Picker)

Combine multiple wheel pickers for complex selections:
import { useState } from "react";
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

// Generate hour options (1-12)
const hours: WheelPickerOption<number>[] = Array.from({ length: 12 }, (_, i) => ({
  label: String(i + 1).padStart(2, "0"),
  value: i + 1,
}));

// Generate minute options (0-59)
const minutes: WheelPickerOption<number>[] = Array.from({ length: 60 }, (_, i) => ({
  label: String(i).padStart(2, "0"),
  value: i,
}));

// AM/PM options
const periods: WheelPickerOption[] = [
  { label: "AM", value: "am" },
  { label: "PM", value: "pm" },
];

export function TimePicker() {
  const [hour, setHour] = useState(12);
  const [minute, setMinute] = useState(0);
  const [period, setPeriod] = useState("am");

  const formattedTime = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")} ${period.toUpperCase()}`;

  return (
    <div>
      <p>Selected time: {formattedTime}</p>
      <WheelPickerWrapper>
        <WheelPicker
          options={hours}
          value={hour}
          onValueChange={setHour}
        />
        <WheelPicker
          options={minutes}
          value={minute}
          onValueChange={setMinute}
          infinite
        />
        <WheelPicker
          options={periods}
          value={period}
          onValueChange={setPeriod}
        />
      </WheelPickerWrapper>
    </div>
  );
}

Infinite Scrolling

Enable infinite loop for options that should wrap around:
import { useState } from "react";
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

const numbers: WheelPickerOption<number>[] = Array.from({ length: 10 }, (_, i) => ({
  label: String(i),
  value: i,
}));

export function InfiniteNumberPicker() {
  const [value, setValue] = useState(0);

  return (
    <WheelPickerWrapper>
      <WheelPicker
        options={numbers}
        value={value}
        onValueChange={setValue}
        infinite
      />
    </WheelPickerWrapper>
  );
}
The infinite prop enables seamless wrapping, perfect for circular data like time, dates, or rotating selections.

Disabled Options

Mark specific options as disabled:
import { useState } from "react";
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

const dates: WheelPickerOption<number>[] = [
  { label: "Jan 1", value: 1 },
  { label: "Jan 2", value: 2, disabled: true },
  { label: "Jan 3", value: 3 },
  { label: "Jan 4", value: 4, disabled: true },
  { label: "Jan 5", value: 5 },
];

export function DatePicker() {
  const [date, setDate] = useState(1);

  return (
    <WheelPickerWrapper>
      <WheelPicker
        options={dates}
        value={date}
        onValueChange={setDate}
      />
    </WheelPickerWrapper>
  );
}
Disabled options are automatically skipped during keyboard navigation and cannot be selected by clicking.

TypeScript with Generic Types

Use TypeScript generics for type-safe value handling:
import { useState } from "react";
import {
  WheelPicker,
  WheelPickerWrapper,
  type WheelPickerOption,
} from "@ncdai/react-wheel-picker";

// Number values
const quantities: WheelPickerOption<number>[] = [
  { label: "1 item", value: 1 },
  { label: "2 items", value: 2 },
  { label: "5 items", value: 5 },
  { label: "10 items", value: 10 },
];

export function QuantityPicker() {
  const [quantity, setQuantity] = useState<number>(1);

  return (
    <WheelPickerWrapper>
      <WheelPicker<number>
        options={quantities}
        value={quantity}
        onValueChange={(value: number) => {
          // value is typed as number
          setQuantity(value);
        }}
      />
    </WheelPickerWrapper>
  );
}

Styling

With Tailwind CSS

Use the classNames prop to apply Tailwind classes:
<WheelPickerWrapper className="w-56 rounded-md border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-950">
  <WheelPicker
    options={options}
    value={value}
    onValueChange={setValue}
    classNames={{
      optionItem: "text-zinc-400 dark:text-zinc-500",
      highlightWrapper:
        "bg-zinc-100 text-zinc-950 dark:bg-zinc-900 dark:text-zinc-50 data-rwp-focused:ring-2 data-rwp-focused:ring-zinc-300",
      highlightItem: "font-medium",
    }}
  />
</WheelPickerWrapper>

With CSS

Target data attributes for CSS-based styling:
[data-rwp-wrapper] {
  width: 14rem;
  border-radius: 0.375rem;
  border: 1px solid #e4e4e7;
}

[data-rwp-option] {
  color: #a1a1aa;
}

[data-rwp-highlight-wrapper] {
  background-color: #f4f4f5;
  color: #09090b;
}

Keyboard Navigation

React Wheel Picker includes comprehensive keyboard support:
Navigate through options one at a time
Move focus between multiple pickers in the same wrapper
Jump to the first or last option (non-infinite mode)

Next Steps

API Reference

Explore all available props and configuration options

Advanced Examples

See complex implementations and real-world use cases

Customization

Learn about styling with classNames and data attributes

TypeScript

Dive deep into type definitions and generics
You’re now ready to build amazing wheel picker interfaces!

Build docs developers (and LLMs) love