From ehmo-platform-design-skills
Apple Human Interface Guidelines for Apple Watch. Use when building watchOS apps, complications, or workout features. Triggers on tasks involving Watch UI, Digital Crown, glanceable interfaces, or wrist-based interactions.
npx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin ehmo-platform-design-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.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
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))