Markdown:
Components

WheelPicker

A wheel-style single select that keeps native select behavior as the source of truth.

Selected 11:00

Usage

Use standalone.

Svelte
<WheelPicker {...props} />

Anatomy

The class attribute values represent part names. Other attributes represent their corresponding props.

Svelte
<div class="whole">
  <select class="main" {...rest}>
    {#each options as option}
      <option value={option.value} disabled={option.disabled}>{option.text}</option>
    {/each}
  </select>
  <div class="middle" aria-hidden="true">
    {#each options as option, index}
      <div class="label">{label ?? option.text}</div>
    {/each}
  </div>
  <div class="aux" aria-hidden="true"></div>
  <div class="extra" aria-hidden="true"></div>
</div>
  • The sr-only select.main remains the source of truth for focus, keyboard input, typeahead, change events, and form submission.
  • middle renders the decorative wheel and writes pointer or wheel movement back to the native select. Tapping a visible row selects that option, or the nearest enabled option if that row is disabled.
  • loop visually wraps movement, but the native select value stays a normal finite option list.
  • aux is the center selection band, and extra is intended for overlays such as top and bottom fades.

Props

NameTypeDefaultDescription
options*WheelOption[]-Selectable values; disabled options remain visible but are skipped when snapping from pointer or wheel input
valuestring-Bindable selected option value; defaults to the first enabled option
loopbooleanfalseEnables visual circular movement when there are at least two options
perspectivenumber-3D wheel perspective in px
maxAnglenumber60Maximum 3D wheel label angle in degrees
labelSnippet-Custom content for each wheel row
cssvarRecord-Mirrors measured values to CSS custom properties on whole
attachAttachment-Svelte attachment on the native select
elementHTMLSelectElement-Bindable native select element
stylingSVSClass-Styling override
variantSVSVariantNEUTRALVariant for non-selected, enabled labels

* required. Other HTMLSelectAttributes are passed to <select> via rest props; class is merged onto the root.

Styling

To learn more, see Styling.

Variant Management

The selected label renders with active. Disabled option labels render with inactive. Other labels use the supplied variant.

Default Class Name

svs-wheelpicker

Behavior

  • The sr-only <select> is the source of truth: focus, arrow keys, typeahead, change, and name submission stay native; it remains focusable through clip-based hiding.
  • .middle is the decorative drum: it is aria-hidden, pointer/wheel gestures write selectedIndex to the select, item height is measured, and visible row count is derived.
  • Tapping a visible drum row selects that option; a tap on a disabled row snaps to the nearest enabled option. This relies on .aux / .extra staying pointer-events: none.
  • loop wraps selectedIndex modularly with seam clones; the <select> itself never loops.
  • cssvar mirrors named keys onto .whole only.

Accessibility

ARIA Roles and Attributes

The component applies these automatically:

  • Select (main <select>): aria-orientation="vertical".
  • Visual layers (middle, aux, and extra): aria-hidden="true" because the native select owns focus, value, and assistive-technology exposure.

Provide an accessible name yourself because there is no built-in visible label. Pass aria-label or aria-labelledby through the rest props, or associate a <label> with the select.

Standards Basis

WheelPicker is built on a native <select> for focus, typeahead, arrow-key navigation, and value changes, so keyboard and assistive-technology support follows the browser standard for select controls. See the Concepts standards-first guidance for the library's approach.

Exports

Types

TypeScript
interface WheelPickerProps extends Omit<HTMLSelectAttributes, "value" | "multiple" | "size" | "style"> {
  options: WheelOption[];
  value?: string; // bindable; first enabled option's value
  loop?: boolean; // (false)
  perspective?: number; // px
  maxAngle?: number; // deg (60)
  label?: Snippet<[WheelOption, string, number]>; // Snippet<[option,variant,index]>
  cssvar?: Partial<Record<WheelCssVar, string>>;
  attach?: Attachment<HTMLSelectElement>;
  element?: HTMLSelectElement; // bindable
  styling?: SVSClass;
  variant?: SVSVariant; // (VARIANT.NEUTRAL)
  // class is merged onto .whole; style is component-owned
}
type WheelPickerReqdProps = "options";
type WheelPickerBindProps = "value" | "element";
type WheelOption = { value: string; text: string; disabled?: boolean };
type WheelCssVar = "itemHeight" | "visible" | "perspective" | "maxAngle";

Others

No additional exports are available.

Examples

The example code uses Tailwind CSS along with SVSClass.

Basic

Selected 11:00

With Interactive Variant

Preset for Demo
Enter variant as you like
Size: md