---
title: "Picker"
description: "A wheel picker that selects one value per column."
category: Form
status: beta
since: 0.3.4
tags: [picker, form, wheel, selection]
syncStatus: reviewed
verified: true
---

## Usage

### Popup, Inline, and Dense Rows
`v-model:visible` controls the popup, `popup="false"` renders inline, and `itemHeight` / `visibleItemCount` size the rows.
:::TuffDemoWrapper{demo="PickerPickerDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import type { PickerColumn, PickerValue } from '@talex-touch/tuffex'
  import { ref } from 'vue'

  const visible = ref(false)
  const value = ref<PickerValue>(['pro', 'monthly'])

  const columns: PickerColumn[] = [
    {
      key: 'plan',
      options: [
        { value: 'free', label: 'Free' },
        { value: 'pro', label: 'Pro' },
        { value: 'team', label: 'Team', disabled: true },
      ],
    },
    {
      key: 'cycle',
      options: [
        { value: 'monthly', label: 'Monthly' },
        { value: 'annual', label: 'Annual' },
      ],
    },
  ]
  </script>

  <template>
    <TxButton @click="visible = true">Open picker</TxButton>
    <TxPicker v-model="value" v-model:visible="visible" title="Subscription plan" :columns="columns" />

    <TxPicker v-model="value" :popup="false" title="Subscription plan" :columns="columns" />

    <TxPicker
      v-model="value"
      :popup="false"
      :show-toolbar="false"
      :columns="columns"
      :item-height="28"
      :visible-item-count="4"
    />
  </template>
---
:::

### Best Practices

- Keep `modelValue` a complete array ordered like `columns`; sparse arrays are normalized, but explicit values keep form state auditable.
- Give each column a `key` when columns reorder or render conditionally, so Vue doesn't reuse an old column for different data.
- Use stable primitive option `value`s and put display text in `label`.
- Use inline mode (`popup=false`) for always-visible settings, and the popup for mobile or short tasks.
- Don't submit disabled values: the component skips disabled options only when it normalizes or recovers a scroll.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `modelValue` | `PickerValue` | `[]` | One value per column; missing or invalid values fall back to the first enabled option. |
| `columns` | `PickerColumn[]` | `[]` | Column definitions, left to right. |
| `visible` | `boolean` | `false` | Popup visibility, bound with `v-model:visible`. |
| `popup` | `boolean` | `true` | `true` teleports a bottom popup; `false` renders inline. |
| `title` | `string` | `''` | Toolbar title. |
| `showToolbar` | `boolean` | `true` | Shows the cancel, title, and confirm toolbar. |
| `confirmText` | `string` | `'Confirm'` | Confirm button text. |
| `cancelText` | `string` | `'Cancel'` | Cancel button text. |
| `disabled` | `boolean` | `false` | Disables the toolbar, the options, and dragging. |
| `itemHeight` | `number` | `36` | Row height in px; at least 24. |
| `visibleItemCount` | `number` | `5` | Visible rows; even counts round up to odd, at least 3. |
| `closeOnClickMask` | `boolean` | `true` | Closes the popup when the mask is clicked. |
| `lazyMount` | `boolean` | `true` | Mounts the popup body on first open. |

### Events

| Event | Params | Description |
|------|------|------|
| `update:modelValue` | `(value: PickerValue)` | Fires when turning a column changes its value. |
| `change` | `(value: PickerValue)` | Fires together with `update:modelValue`. |
| `update:visible` | `(visible: boolean)` | Fires on open and close. |
| `confirm` | `(value: PickerValue)` | Fires with the current value on confirm, then closes. |
| `cancel` | - | Fires on cancel, then closes. |
| `open` | - | Fires on open. |
| `close` | - | Fires on close. |

### Exposed Methods

| Name | Type | Description |
|------|------|------|
| `open` | `() => void` | Opens the popup by writing `visible=true`. |
| `close` | `() => void` | Closes the popup. |
| `toggle` | `() => void` | Toggles the popup. |

### Types

```ts
type PickerValue = Array<string | number>
```

`PickerColumn`, one entry of `columns`:

| Field | Type | Description |
|------|------|------|
| `key` | `string` | Optional stable key. |
| `options` | `PickerOption[]` | The column's options. |

`PickerOption`, one entry of `options`:

| Field | Type | Description |
|------|------|------|
| `value` | `string \| number` | Primitive value emitted for the column. |
| `label` | `string` | Visible text. |
| `disabled` | `boolean` | Prevents selection; skipped by the normalization fallback. |

## Overview

- Each column is a drum the component turns itself, not a native scroller; it handles the pointer, the wheel, clicks, and the keyboard.
- A drag tracks the pointer, then coasts on its release velocity and settles on the nearest enabled row with a cubic ease-out; the wheel settles 120ms after it stops.
- Under reduced motion a drag still tracks the pointer, but there is no coast and settling doesn't ease.
- When a controlled parent echoes back the value a column is already turning to, the column finishes its turn instead of being re-placed.
- Only the rows facing the viewer are drawn (11 at the default size, whatever the column length); each carries `aria-setsize` and `aria-posinset`, so screen readers see the full list.

## Technologies

- Rows sit on the drum by `rotateX` of their distance from the offset (defaults: `r = 116px`, a 17.6° step, a 348px perspective); rows skip hit testing, and a click resolves to its row by inverting the projection in closed form.
- Source: `packages/tuffex/packages/components/src/picker/`.

<TuffDocSourceLink />
