Skip to main content
iOS / SwiftUI · WCAG 2.2 AA

Select (Wheel)

A SwiftUI Picker with the wheel style, an always-visible spinning drum of values that requires an accessibilityLabel matching its visible label plus accessibilityElement(children:.contain) for VoiceOver to speak the name.

Selection

The criteria an agent checks before retrieving this component.

Use when

  • Use when the user chooses one value from a fixed set shown as an always-visible spinning drum the user scrolls in place (e.g., a quantity, a unit, a short list of options). Uses a SwiftUI Picker with .pickerStyle(.wheel).
  • Use when the option set is best browsed by scrolling and the wheel is meant to stay on screen rather than pop up.

Try a different component when

  • Do not use when the choice should collapse into a compact pop-up button that shows only the current value (use select.menu).
  • Do not use when the choice is among two to five options that fit inline as segments (use select.segmented).
  • Do not use when the value is a date or time (use date-picker.basic, which provides the correct wheel semantics and formatting).
  • Do not use when the items are commands or actions rather than a single selected value (use menu.basic).

Must Haves

Non-negotiable structure. Every generated instance must satisfy these rules.

  • Use a native Picker with .pickerStyle(.wheel) so it exposes the wheel's role, the current value, and the option list to VoiceOver and Switch Control.
  • Add an .accessibilityLabel whose text matches the picker's visible label (e.g., .accessibilityLabel("Fruit")), and set .accessibilityElement(children: .contain) on the picker. Without both, VoiceOver speaks neither the Picker label text nor the .accessibilityLabel, leaving the wheel unnamed (WCAG 4.1.2).
  • This is the opposite of the menu and default styles: a menu-style Picker is named by its Picker label text and must not carry an .accessibilityLabel (see select.menu), whereas the wheel style requires the .accessibilityLabel plus .contain to be named at all.
  • Provide a visible label for the wheel so sighted users know what is being chosen, and keep the .accessibilityLabel matching it (WCAG 2.5.3, 3.3.2).
  • Each option has clear, distinct text so the values are distinguishable as they scroll under VoiceOver.
  • Meets the touch target size baseline in global_rules.md (global.touch-target-size).
  • Meets the system focus indicator baseline in global_rules.md (global.focus-visible).

Donts

Avoid these accessibility and UX barriers.

  • Do not omit .accessibilityElement(children: .contain) on a wheel-style Picker; without it the accessibility label is not spoken and the wheel is announced with no name.
  • Do not rely on the Picker label text alone to name a wheel-style picker; unlike the menu and default styles, wheel-style ignores it for VoiceOver naming.
  • Do not leave the wheel unlabeled (Picker("", ...) with no .accessibilityLabel); VoiceOver then has no accessible name for it.
  • Do not use a wheel-style value Picker for a list of commands or actions; that is a menu.basic.

Customizable

Alternatives and options that give the AI agent some room to move.

  • The current selection may also be shown in a separate visible Text (e.g., "Fruit: Apple") above the wheel, as long as it stays in sync with the binding.
  • The option set may be static (a ForEach over a CaseIterable enum) or dynamic, as long as each option has stable, distinct text and a .tag matching the selection type.
  • The visible label may be supplied by the Picker label text, a preceding Text, or both; the .accessibilityLabel must match whichever visible text names the wheel.

Golden Pattern

The tested reference implementation. Agents start from this shape and adapt to the developer’s codebase and context.

SWIFT
import SwiftUI

struct SelectWheelDemo: View {
enum Fruit: String, CaseIterable, Identifiable {
case apple = "Apple", banana = "Banana", cherry = "Cherry", grape = "Grape"
var id: Self { self }
}

@State private var fruit: Fruit = .apple

var body: some View {
// Wheel style names itself ONLY with .accessibilityLabel + .contain.
// This is the opposite of the menu style, which is named by its Picker label text.
Picker("Fruit", selection: $fruit) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit)
}
}
.pickerStyle(.wheel)
.accessibilityElement(children: .contain) // required, or the label is not spoken
.accessibilityLabel("Fruit") // must match the visible label text
}
}

Acceptance Checks

The component’s test spec — an optional body of checks for verification.

Traits & semantics

  • The wheel exposes an accessible name equal to its visible label, sourced from the .accessibilityLabel combined with .accessibilityElement(children: .contain); it is not left unnamed.

VoiceOver

  • Focusing the wheel speaks the picker name and the current value; without the .accessibilityLabel plus .contain the name is silent, which is the failure this pattern prevents.
  • Scrolling the wheel updates the announced value to the option that lands in the selection position.

Switch Control & Full Keyboard Access

  • The wheel is reachable and operable via Switch Control and a hardware keyboard: it takes focus and its value can be changed.
  • Voice Control users cannot say "Tap

Dynamic Type

  • The wheel's label and option text scale with Dynamic Type and stay legible at accessibility text sizes.
  • Wheel-style pickers do not support the Large Content Viewer, an Apple platform defect, so the enlarged overlay is unavailable when inspecting a wheel option.

Visual

  • Non-selected wheel options render with insufficient text contrast; this is an Apple platform defect (Feedback FB15338784) that a correct implementation cannot fully remedy.