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
Pickerwith.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
Pickerwith.pickerStyle(.wheel)so it exposes the wheel's role, the current value, and the option list to VoiceOver and Switch Control. - Add an
.accessibilityLabelwhose text matches the picker's visible label (e.g.,.accessibilityLabel("Fruit")), and set.accessibilityElement(children: .contain)on the picker. Without both, VoiceOver speaks neither thePickerlabel 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
Pickeris named by itsPickerlabel text and must not carry an.accessibilityLabel(seeselect.menu), whereas the wheel style requires the.accessibilityLabelplus.containto be named at all. - Provide a visible label for the wheel so sighted users know what is being chosen, and keep the
.accessibilityLabelmatching 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-stylePicker; without it the accessibility label is not spoken and the wheel is announced with no name. - Do not rely on the
Pickerlabel 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
Pickerfor a list of commands or actions; that is amenu.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
ForEachover aCaseIterableenum) or dynamic, as long as each option has stable, distinct text and a.tagmatching the selection type. - The visible label may be supplied by the
Pickerlabel text, a precedingText, or both; the.accessibilityLabelmust 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.
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
.accessibilityLabelcombined with.accessibilityElement(children: .contain); it is not left unnamed.
VoiceOver
- Focusing the wheel speaks the picker name and the current value; without the
.accessibilityLabelplus.containthe 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.