Skip to main content
Version: Next 🚧

MultiWheelPicker

MultiWheelPicker puts several React Native WheelPickers side by side, sharing one selection band, the way iOS shows time and date wheels. TimePicker and DateWheelPicker are built on it. Use it for durations, heights, weights, currency + amount, or anything multi-column.

import { MultiWheelPicker } from '@elik56a/react-native-pickers';
Result
Loading...
Live Editor
import { useState } from 'react';
import { MultiWheelPicker } from '@elik56a/react-native-pickers';

const range = (n: number) => Array.from({ length: n }, (_, i) => ({ label: String(i).padStart(2, '0'), value: i }));

export default function Example() {
  const [h, setH] = useState(1);
  const [m, setM] = useState(30);
  const [s, setS] = useState(0);
  return (
    <MultiWheelPicker
      separator=":"
      columns={[
        { key: 'h', items: range(24), value: h, onChange: setH },
        { key: 'm', items: range(60), value: m, onChange: setM, loop: true },
        { key: 's', items: range(60), value: s, onChange: setS, loop: true },
      ]}
    />
  );
}

Try the props​

variant
visibleItems
itemHeight
<MultiWheelPicker columns={columns} separator=":" />

Columns​

Each entry in columns is a WheelPicker's props (minus the shared ones below) plus layout keys:

type WheelColumn<T> = Omit<WheelPickerProps<T>, 'itemHeight' | 'visibleItems' | 'variant' | 'showIndicator' | 'theme'> & {
key: string; // React key, required
flex?: number; // flex weight, default 1
width?: number; // fixed width instead of flex
};

So every column has its own items, value, onChange, onValueChanging, loop, renderItem, keyboardInput, isEqual, textAlign, disabled, styles and so on. itemHeight, visibleItems, variant and theme are set once on the MultiWheelPicker so all columns line up.

Labels and separators​

separator is placed between every pair of columns. A string is rendered as bold text (style it with the separator slot), and any React node is rendered as-is. Pass a function to choose per gap: it gets the index of the column on the left and can return null to skip that gap.

Result
Loading...
Live Editor
import { useState } from 'react';
import { Text } from 'react-native';
import { MultiWheelPicker } from '@elik56a/react-native-pickers';

const feet = [3, 4, 5, 6, 7].map((v) => ({ label: `${v} ft`, value: v }));
const inches = Array.from({ length: 12 }, (_, i) => ({ label: `${i} in`, value: i }));

export default function Example() {
  const [ft, setFt] = useState(5);
  const [inch, setInch] = useState(9);
  return (
    <>
      <MultiWheelPicker
        columns={[
          { key: 'ft', items: feet, value: ft, onChange: setFt, textAlign: 'right' },
          { key: 'in', items: inches, value: inch, onChange: setInch, textAlign: 'left' },
        ]}
      />
      <Text style={{ textAlign: 'center', color: '#6E685E' }}>
        {ft}′{inch}″ = {Math.round((ft * 12 + inch) * 2.54)} cm
      </Text>
    </>
  );
}

Column widths​

Use flex for proportional columns and width for fixed ones:

Result
Loading...
Live Editor
import { useState } from 'react';
import { MultiWheelPicker } from '@elik56a/react-native-pickers';

const amounts = Array.from({ length: 100 }, (_, i) => (i + 1) * 10);

export default function Example() {
  const [cur, setCur] = useState('USD');
  const [amount, setAmount] = useState(250);
  return (
    <MultiWheelPicker
      variant="flat"
      columns={[
        { key: 'cur', items: ['USD', 'EUR', 'GBP', 'ILS'], value: cur, onChange: setCur, width: 90 },
        { key: 'amount', items: amounts, value: amount, onChange: setAmount, flex: 2 },
      ]}
    />
  );
}

Dependent columns​

Columns are plain props, so derive one from another in render. Here the model list depends on the brand:

Result
Loading...
Live Editor
import { useState } from 'react';
import { MultiWheelPicker } from '@elik56a/react-native-pickers';

const models: Record<string, string[]> = {
  Apple: ['iPhone 15', 'iPhone 16', 'iPhone 17'],
  Google: ['Pixel 8', 'Pixel 9', 'Pixel 10'],
  Samsung: ['Galaxy S24', 'Galaxy S25', 'Galaxy Z Fold'],
};

export default function Example() {
  const [brand, setBrand] = useState('Google');
  const [model, setModel] = useState('Pixel 9');
  return (
    <MultiWheelPicker
      columns={[
        {
          key: 'brand',
          items: Object.keys(models),
          value: brand,
          onChange: (b) => {
            setBrand(b);
            setModel(models[b][0]);
          },
        },
        { key: 'model', items: models[brand], value: model, onChange: setModel, flex: 1.4 },
      ]}
    />
  );
}

Props​

PropTypeDefaultDescription
columnsWheelColumn[]requiredOne entry per wheel; see Columns.
separatorReactNode | ((leftIndex: number) => ReactNode)—Content between columns. Strings use the separator slot style.
itemHeightnumber40Row height shared by all columns.
visibleItemsnumber5Visible rows (odd) shared by all columns.
variant'3d' | 'flat''3d'Wheel style for all columns.
styleStyleProp<ViewStyle>—Outer container style.
stylesSlotStyles<MultiWheelSlot>—Slot styles, applied to every column (a column's own styles win).
themeDeepPartial<PickerTheme>—Theme override for all columns.
testIDstring—Test ID on the container.

Slots​

SlotElement
containerOuter view
indicatorThe shared selection band
separatorString separators
item, itemText, selectedItemText, disabledItemTextForwarded to every column's wheel
type MultiWheelSlot = 'container' | 'indicator' | 'separator' | WheelPickerSlot;