Select (Menu)
A SwiftUI Picker with the menu style, a pop-up button that shows the current value and opens a list to choose one option, named by the Picker label text rather than an accessibilityLabel.
Selection
The criteria an agent checks before retrieving this component.
Use when
- Use when the user chooses one value from a fixed set and the current choice is shown on a compact pop-up button (e.g., "Sort by", "Fruit", "Country"). Uses a SwiftUI
Pickerwith.pickerStyle(.menu). - Use when the option set is longer than fits a segmented control but does not need a full-screen wheel.
Try a different component when
- Do not use when the items are commands or actions rather than a single selected value (use
menu.basic). - Do not use when the choice is among two to five options that fit inline (use
select.segmented). - Do not use when a spinning wheel of values is intended (use
select.wheel). - Do not use when the value is free text the user types (use
text-field.basic).
Must Haves
Non-negotiable structure. Every generated instance must satisfy these rules.
- Use a native
Pickerwith.pickerStyle(.menu)so it exposes the pop-up button role, the current value, and the option list to VoiceOver and Switch Control. - Name the picker with the
Pickerlabel text (Picker("Fruit", selection: $fruit)), which becomes the accessible name for the menu and default styles. - Do not add an
.accessibilityLabelto a menu-style or default-stylePicker; it suppresses VoiceOver speaking the selected value when the picker is closed. The wheel and segmented styles are the opposite case, covered byselect.wheelandselect.segmented. - Each option has clear, distinct text so the choices are distinguishable in the pop-up list.
- After the value changes, return VoiceOver focus to the picker with
@AccessibilityFocusStateand.accessibilityFocused, becausePickerhas no dismissal callback to restore focus (WCAG 2.4.3). - Provide a visible indication of what is being chosen when the on-button value alone does not convey it (WCAG 3.3.2); the
Pickerlabel text can serve this, or a separateTextcan precede the picker. - 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 add
.accessibilityLabelto a menu-style or default-stylePicker; it stops VoiceOver from announcing the selected value when closed. Name it through thePickerlabel text instead. - Do not leave the
Pickerlabel empty (Picker("", ...)) on a menu style; VoiceOver then has no accessible name. - Do not use a value
Pickerfor a list of commands or actions; that is amenu.basic. - Do not omit focus return after selection; without
@AccessibilityFocusState, VoiceOver focus can jump away from the picker when the value changes.
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") in addition to the on-button value, 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.
Golden Pattern
The tested reference implementation. Agents start from this shape and adapt to the developer’s codebase and context.
import SwiftUI
struct SelectMenuDemo: 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
@AccessibilityFocusState private var pickerFocused: Bool
var body: some View {
// The Picker label ("Fruit") is the accessible name; do NOT add .accessibilityLabel here.
Picker("Fruit", selection: $fruit) {
ForEach(Fruit.allCases) { fruit in
Text(fruit.rawValue).tag(fruit)
}
}
.pickerStyle(.menu)
.accessibilityFocused($pickerFocused)
.onChange(of: fruit) {
pickerFocused = true // return VoiceOver focus to the picker after the value changes
}
}
}
Acceptance Checks
The component’s test spec — an optional body of checks for verification.
Traits & semantics
- The picker is a pop-up button exposing its accessible name (from the
Pickerlabel text) and its current value; no.accessibilityLabeloverrides it.
VoiceOver
- When closed, VoiceOver speaks the picker name and the selected value (e.g., "Fruit, Apple, Pop Up Button").
- Opening the picker lists the options, and selecting one updates the announced value.
- After a selection, VoiceOver focus returns to the picker rather than jumping elsewhere.
Switch Control & Full Keyboard Access
- The picker is reachable and operable via Switch Control and a hardware keyboard: it opens, an option can be chosen, and it closes.
Dynamic Type
- The picker's label, on-button value, and option list scale with Dynamic Type and stay fully visible at accessibility text sizes.