npx claudepluginhub dirnbauer/webconsulting-skillsThis skill uses the workspace's default tool permissions.
Apple Watch is a personal, glanceable device worn on the wrist. Interactions are measured in seconds, not minutes. Every design decision must prioritize speed of comprehension and brevity of interaction.
Applies Apple watchOS Human Interface Guidelines for glanceable UI, Digital Crown interactions, complications, and workout features in Watch apps.
Guides watchOS app development with SwiftUI, Watch Connectivity, complications, WidgetKit, HealthKit, Extended Runtime, and watch UI patterns for code review and best practices.
Provides Apple HIG guidelines for system components: widgets, live activities, notifications, complications, home screen quick actions, top shelf, watch faces, app clips, app shortcuts. Useful for Apple platform UI design.
Share bugs, ideas, or general feedback.
Apple Watch is a personal, glanceable device worn on the wrist. Interactions are measured in seconds, not minutes. Every design decision must prioritize speed of comprehension and brevity of interaction.
The defining constraint of watchOS. If a user cannot extract the key information within 2 seconds of raising their wrist, the design has failed.
| Device | Screen Width | Screen Height | Corner Radius |
|---|---|---|---|
| 41mm (Series 9) | 176px | 215px | 36px |
| 45mm (Series 9) | 198px | 242px | 39px |
| 42mm (Series 10) | 180px | 220px | 37px |
| 46mm (Series 10) | 205px | 251px | 40px |
| 49mm (Ultra 2) | 205px | 251px | 40px |
The Digital Crown is the primary physical input for scrolling and precise value selection. It provides haptic feedback and should feel purposeful.
Correct — Crown binding with haptic detents:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
VStack {
Text("\(Int(volume * 100))%")
.font(.title.bold())
Image(systemName: "speaker.wave.3")
}
.focusable()
.digitalCrownRotation(
$volume,
from: 0.0,
through: 1.0,
by: 0.05,
sensitivity: .medium,
isContinuous: false,
isHapticFeedbackEnabled: true
)
}
}
Incorrect — ignoring the Crown and forcing touch-only interaction:
struct VolumePickerView: View {
@State private var volume: Double = 0.5
var body: some View {
Slider(value: $volume)
// No .digitalCrownRotation — Crown input is ignored
// Users must use touch-only, which is imprecise and frustrating on Watch
}
}
Watch navigation must be shallow and predictable. Users should never feel lost or unable to return to a known state.
TabView for top-level sections (max 5 tabs). Swipe horizontally between tabs. Each tab is a distinct functional area.NavigationStack for hierarchical drill-down. Limit hierarchy to 2-3 levels maximum. Every pushed view must have a back button (provided automatically by the system).| Pattern | Use Case | Gesture |
|---|---|---|
| Vertical scroll | Long-form content within a single view | Digital Crown / swipe up-down |
| TabView (horizontal pages) | Top-level app sections | Swipe left-right |
| NavigationStack (push/pop) | Hierarchical drill-down | Tap to push, swipe right or back button to pop |
| Modal sheet | Confirmation, focused input | Presented programmatically, dismiss via button or swipe down |
Complications are the most visible surface of a Watch app. They live on the watch face and provide at-a-glance data without launching the app.
accessoryCircular, accessoryCorner, and accessoryRectangular (WidgetKit, watchOS 9+).TimelineProvider. Provide future timeline entries when data is predictable (e.g., next calendar event, weather forecast). Keep data fresh -- stale complications erode trust.Correct — WidgetKit TimelineProvider for an accessoryCircular complication:
struct StepCountProvider: TimelineProvider {
func placeholder(in context: Context) -> StepEntry {
StepEntry(date: Date(), steps: 5000)
}
func getSnapshot(in context: Context, completion: @escaping (StepEntry) -> Void) {
completion(StepEntry(date: Date(), steps: HealthStore.shared.todaySteps))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<StepEntry>) -> Void) {
let entry = StepEntry(date: Date(), steps: HealthStore.shared.todaySteps)
// Refresh in 15 minutes
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
}
}
struct StepCountComplicationView: View {
let entry: StepEntry
var body: some View {
Gauge(value: Double(entry.steps), in: 0...10000) {
Image(systemName: "figure.walk")
} currentValueLabel: {
Text("\(entry.steps / 1000)k")
}
.gaugeStyle(.accessoryCircular)
}
}
Use WidgetFamily values:
| Family | Shape | Typical Content |
|---|---|---|
accessoryCircular | Small circle | Single value, icon, or gauge |
accessoryCorner | Curved, top corners | Gauge with label, or text with icon |
accessoryRectangular | Wide rectangle | Multi-line text, chart, or detailed view |
accessoryInline | Text row | Short label or value |
When the user's wrist is down, watchOS enters an Always On state showing a dimmed version of the current app. This must be handled intentionally.
TimelineView with a .everyMinute schedule for time-sensitive content.Workout and health apps have unique requirements: extended sessions, live metrics, and body-awareness features.
Watch notifications must be brief and actionable. The user's wrist is raised for only a moment.
.notification for standard alerts, .directionUp for positive events, .directionDown for negative events, .success/.failure/.retry for outcomes.| Haptic | Use Case |
|---|---|
.notification | General alerts |
.directionUp | Positive event (goal reached, stock up) |
.directionDown | Negative event (stock down, weather warning) |
.success | Action completed successfully |
.failure | Action failed |
.retry | Try again prompt |
.start | Activity beginning |
.stop | Activity ending |
.click | Discrete selection (Crown detent, picker) |
Apple Watch supports VoiceOver and other assistive technologies. Complications and app UI must be accessible.
.accessibilityLabel() on image-only buttons..accessibilityValue() and .accessibilityHint().@Environment(\.accessibilityReduceMotion).@Environment(\.legibilityWeight).@Environment(\.colorSchemeContrast) to detect the user's preference.Correct:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
.accessibilityLabel("Start workout")
Incorrect:
Button(action: startWorkout) {
Image(systemName: "play.fill")
}
// VoiceOver reads "play" — not clear what action this performs
Use this checklist when reviewing a watchOS design or implementation.
.accessibilityValue() / .accessibilityHint()@Environment(\.accessibilityReduceMotion))@Environment(\.legibilityWeight))This skill is based on the excellent work by ehmo.
Original repository: https://github.com/ehmo/platform-design-skills
Special thanks to ehmo for their generous open-source contributions, which helped shape this skill collection. Adapted by webconsulting.at for this skill collection