Component Patterns (watchOS)
BUTTON HIERARCHY (watch-sized):
| Level | Style | Use Case |
|---|---|---|
| Primary | .borderedProminent |
Main action (Start, Save) |
| Secondary | .bordered |
Alternative (Cancel, Edit) |
| Destructive | .borderedProminent + .tint(.red) |
Delete, Remove |
- Buttons naturally fill width on watchOS — no
.frame(maxWidth: .infinity)needed - Use
.controlSize(.mini)or.controlSize(.small)for compact button groups - ONE
.borderedProminentper screen - ALWAYS use
Button()— never.onTapGesturefor actions
// Primary action
Button("Start") { start() }
.buttonStyle(.borderedProminent)
// Compact button pair
HStack {
Button("Skip") { skip() }
.buttonStyle(.bordered)
.controlSize(.small)
Button("Next") { next() }
.buttonStyle(.borderedProminent)
.controlSize(.small)
}
LIST PATTERNS:
// Grouped list (primary pattern for watchOS)
List {
Section("Today") {
ForEach(todayItems) { item in
NavigationLink(value: item) {
HStack {
Image(systemName: item.icon)
Text(item.title)
Spacer()
Text(item.value)
.foregroundStyle(.secondary)
}
}
}
}
Section("Settings") {
Toggle("Notifications", isOn: $notifications)
}
}
TOGGLE:
// Toggle in a list section
Section("Preferences") {
Toggle("Sound", isOn: $soundEnabled)
Toggle("Haptics", isOn: $hapticsEnabled)
}
PICKER:
// Wheel picker (good for watch)
Picker("Speed", selection: $speed) {
Text("Slow").tag(Speed.slow)
Text("Medium").tag(Speed.medium)
Text("Fast").tag(Speed.fast)
}
// Navigation link picker for many options
Picker("Category", selection: $category) {
ForEach(categories) { cat in
Text(cat.name).tag(cat)
}
}
PROGRESS:
// Circular progress (fits watch aesthetic)
ProgressView(value: progress, total: 1.0)
.progressViewStyle(.circular)
// Linear progress
ProgressView(value: 0.6)
// Indeterminate
ProgressView("Loading...")
GAUGE (watch-native):
Gauge(value: heartRate, in: 40...200) {
Text("BPM")
} currentValueLabel: {
Text("\(Int(heartRate))")
} minimumValueLabel: {
Text("40")
} maximumValueLabel: {
Text("200")
}
.gaugeStyle(.accessoryCircular)
EMPTY STATES:
ContentUnavailableView(
"No Workouts",
systemImage: "figure.run",
description: Text("Start a workout to see it here")
)
DATE/TIME DISPLAY:
// Use system date formatting
Text(Date.now, style: .time)
.font(AppTheme.Fonts.title2)
Text(Date.now, style: .relative)
.font(AppTheme.Fonts.caption)
NOT AVAILABLE ON watchOS:
- No card patterns with shadows (watch doesn't use card-based UI)
- No
.textFieldStyle(.roundedBorder)— watch text input is system-driven - No
.redacted(reason: .placeholder)skeleton loading (too complex for watch) - No full-width
.controlSize(.large)buttons — watch buttons fill width by default - No
.popoverpresentations
RULES:
- Keep components minimal — watch screen is ~40mm
- Use
Listwith sections as the primary content container - Gauge is the watch-native way to show metrics — prefer it over custom progress views
- Text input on watch is limited — prefer selection (Picker, Toggle) over TextField
- Use SF Symbols at appropriate sizes —
.font(.title3)for icons in lists