# Option Wheel

A list of options laid out on a curve, like the edge of a dial. The option in the middle is selected; the others tilt away, fade and blur with distance. Scroll, drag up and down, click an option or use the arrow keys to turn it. An optional tick sound plays on every change.

<demo-option-wheel>



</demo-option-wheel>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/option-wheel.json"
```

## Usage

```vue
<script setup lang="ts">
  import OptionWheel from '~/components/ui/option-wheel/OptionWheel.vue';

  function onChange(index: number, item: string) {
    console.log(index, item);
  }
</script>

<template>
  <div class="relative h-125 w-full">
    <OptionWheel
      :items="['Ambient', 'House', 'Techno', 'Jazz', 'Lo-Fi', 'Synthwave']"
      :default-selected="3"
      side="left"
      :tilt="6"
      sound-url="/sounds/click-soft.mp3"
      @change="onChange"
    />
  </div>
</template>
```

The wheel fills its container, so give the parent a height. One rAF loop eases the position toward its target and only runs while the wheel is moving.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        string[]
      </code>
    </td>
    
    <td>
      12 music genres
    </td>
    
    <td>
      Labels rendered as the wheel options.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        default-selected
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Index of the option selected on mount.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#a6a6a6'
      </code>
    </td>
    
    <td>
      Resting color of the option labels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#ffffff'
      </code>
    </td>
    
    <td>
      Color an option blends toward as it reaches the middle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        side
      </code>
    </td>
    
    <td>
      <code>
        'left' | 'right'
      </code>
    </td>
    
    <td>
      <code>
        'left'
      </code>
    </td>
    
    <td>
      Edge of the container the wheel curves around.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        font-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Font size of the labels in rem.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spacing
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.4
      </code>
    </td>
    
    <td>
      Distance between options as a multiple of the font size.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        curve
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Depth of the curve; 0 flattens the wheel into a straight list.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        6
      </code>
    </td>
    
    <td>
      Angle in degrees between neighbouring options; higher curls tighter.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blur
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Blur in px added per step away from the middle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.25
      </code>
    </td>
    
    <td>
      Opacity lost per step away from the middle.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        min-opacity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.05
      </code>
    </td>
    
    <td>
      Opacity floor for the furthest options.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        smoothing
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Easing time constant in ms; higher values feel heavier.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inset
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        80
      </code>
    </td>
    
    <td>
      Padding in px between the anchored edge and the options.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        loop
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Wrap around instead of stopping at the first and last option.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        draggable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Allow dragging with a pointer, in addition to scroll and keys.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sound-url
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      URL of a short tick played when the selection changes; empty = off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sound-volume
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      Playback volume of the tick, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes on the root.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        change
      </code>
    </td>
    
    <td>
      <code>
        (index: number, item: string)
      </code>
    </td>
    
    <td>
      The wheel moved to a new option.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The root is a focusable `listbox`; each label is an `option` with `aria-selected` on the middle one.
- Arrow Up / Left and Arrow Down / Right step one option. The focused wheel shows a ring in the active color.
- With `prefers-reduced-motion: reduce`, the wheel jumps to each option instead of easing.
- The tick sound is off unless you pass `sound-url`, and playback failures (autoplay policies) are ignored.
- Ported from [React Bits](https://reactbits.dev/components/option-wheel) by David Haz.
