Button (Basic)
SwiftUI Button that triggers an action. Supports text-only, icon+text, and icon-only labeling patterns.
Selection
The criteria an agent checks before retrieving this component.
Use when
- Use when the user triggers an immediate action and stays on the current screen (e.g., "Save", "Add to List", "Play"), including opening a popover, modal, or bottom sheet.
Try a different component when
- Do not use when the control represents an on/off pressed state (use
button.toggle). - Do not use when the control drills into a new in-app screen (use
list.row). - Do not use when the control opens a URL or leaves the app (use
link.basic).
Must Haves
Non-negotiable structure. Every generated instance must satisfy these rules.
- Use a native
Buttonfor built-in VoiceOver semantics, the.isButtontrait, and Switch Control / external-keyboard activation. - The button has an accessible name that describes its purpose or action.
- When the button has visible text, the visible text serves as the accessible name.
- When additional context is needed beyond the visible text, add it via
.accessibilityLabel, with the visible text appearing at the start of the label. - For icon-only buttons, provide an accessible name via
.accessibilityLabel. - When multiple buttons on the same screen share visible text (e.g., a repeated "Edit" button per row), give each one a unique, specific
.accessibilityLabel(e.g., "Edit Username") so VoiceOver users can distinguish them. - Keep icons inside a button decorative to VoiceOver.
Image(systemName:)inside aButtonis decorative by default; label the button as a whole rather than adding a separate.accessibilityLabelto the icon. - If the action is unavailable, disable the button with
.disabled(true)so it becomes non-interactive and is announced as dimmed. - 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 include the word "Button" in
.accessibilityLabel. VoiceOver already appends the "Button" trait, so this makes it announce "Button, Button". - Do not create icon-only buttons without an
.accessibilityLabel(no unlabeled icons). - Do not use an
.accessibilityLabelthat conflicts with (or is wildly different from) the visible label text. Accessible names should at least begin with the visible label.
Customizable
Alternatives and options that give the AI agent some room to move.
- An
.accessibilityHintmay be added when the result of the action is not obvious from the visible label (e.g., a song title that plays on tap might use "Plays the song."). Most buttons with a clear action label ("Save", "Delete") do not need one. Hints describe the result in third-person singular, never the gesture or control type ("button", "tap").
Golden Pattern
The tested reference implementation. Agents start from this shape and adapt to the developer’s codebase and context.
import SwiftUI
struct ButtonBasicDemo: View {
var body: some View {
VStack(spacing: 16) {
// Text-only
Button("Save") {
print("Saved")
}
// Icon + text
Button {
print("Downloaded")
} label: {
Label("Download", systemImage: "arrow.down.circle")
}
// Icon-only (must have accessible name)
Button {
print("Settings")
} label: {
Image(systemName: "gearshape")
}
.accessibilityLabel("Open settings")
// Disabled
Button("Disabled") {
print("Won't fire")
}
.disabled(true)
}
}
}
Acceptance Checks
The component’s test spec — an optional body of checks for verification.
Traits & semantics
- Each button exposes the button trait (VoiceOver reads the name, then "Button").
- Icon-only and icon+text buttons expose a single button element; the icon is not surfaced as its own labeled element.
VoiceOver
- Each button speaks a name that matches its purpose; an icon-only button speaks its
.accessibilityLabel(e.g., "Open settings"), never silence or "button" alone. - No button's spoken name contains the word "Button".
- Buttons that share visible text (a repeated "Edit") each speak a distinct name.
- Double-tapping a button performs its action; a disabled button is announced as dimmed and does not activate.
Switch Control & Full Keyboard Access
- Every enabled button is reachable and activatable via Switch Control and a hardware keyboard; a disabled button is skipped by focus.
Dynamic Type
- Button text scales with Dynamic Type and stays fully visible (no clipping or truncation) at accessibility text sizes.