Skillshub axiom-alarmkit-ref
Use when implementing alarm functionality, scheduling wake alarms, or integrating AlarmKit with Live Activities. Covers AlarmKit authorization, alarm configuration, SwiftUI views, and Live Activity integration.
git clone https://github.com/ComeOnOliver/skillshub
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/CharlesWiltgen/Axiom/axiom-alarmkit-ref" ~/.claude/skills/comeonoliver-skillshub-axiom-alarmkit-ref && rm -rf "$T"
skills/CharlesWiltgen/Axiom/axiom-alarmkit-ref/SKILL.mdAlarmKit Reference
Complete API reference for AlarmKit, Apple's framework for scheduling alarms and countdown timers with system-level alerting, Dynamic Island integration, and focus/silent mode override.
Overview
AlarmKit lets apps create alarms and timers that behave like the built-in Clock app -- they override Do Not Disturb, appear in the Dynamic Island, and show on the Lock Screen. The framework handles scheduling, snooze, pause/resume, and UI presentation through a small set of types centered on
AlarmManager.
System Requirements
- iOS 26+ (AlarmKit introduced in iOS 26)
- Widget Extension required for Live Activity / Dynamic Island presentation
- Physical device recommended for alarm sound and notification testing
Part 1: Key Components
AlarmManager
Singleton entry point for all alarm operations.
import AlarmKit let manager = AlarmManager.shared
All scheduling, cancellation, and observation flows through this shared instance.
Alarm
Describes an alarm that can alert once or on a repeating schedule.
struct Alarm { var id: UUID var schedule: Schedule? var countdownDuration: CountdownDuration? var state: AlarmState }
AlarmPresentation
Content for the alarm UI across three states -- alerting, counting down, and paused.
struct AlarmPresentation { var alert: Alert // Required: shown when alarm fires var countdown: Countdown? // Optional: shown during countdown var paused: Paused? // Optional: shown when paused }
AlarmAttributes
Generic container pairing presentation with app-specific metadata and tint color. Used to configure the Live Activity widget.
struct AlarmAttributes<Metadata: AlarmMetadata> { var presentation: AlarmPresentation var metadata: Metadata var tintColor: Color }
AlarmMetadata
Protocol for app-specific data attached to an alarm. Conform an empty struct for minimal usage, or add properties for richer UI.
struct RecipeMetadata: AlarmMetadata { let recipeName: String let cookingStep: String }
Part 2: Authorization
Apps must request permission before scheduling alarms. Add
NSAlarmKitUsageDescription to Info.plist.
Requesting Authorization
func requestAlarmAuthorization() async -> Bool { do { let state = try await AlarmManager.shared.requestAuthorization() return state == .authorized } catch { print("Authorization error: \(error)") return false } }
Checking Current State
Use
authorizationState (not authorizationStatus) to read the current value:
let state = await AlarmManager.shared.authorizationState // .authorized | .denied | .notDetermined
Observing Authorization Changes
for await authState in AlarmManager.shared.authorizationUpdates { switch authState { case .authorized: enableAlarmUI() case .denied: showPermissionPrompt() case .notDetermined: break @unknown default: break } }
Part 3: Scheduling Alarms
Every alarm requires a
UUID, an AlarmManager.AlarmConfiguration, and a call to schedule(id:configuration:).
One-Time Alarm
let id = UUID() let time = Alarm.Schedule.Relative.Time(hour: 7, minute: 30) let schedule = Alarm.Schedule.relative(.init( time: time, repeats: .never )) let alert = AlarmPresentation.Alert( title: "Wake Up", stopButton: .stopButton, secondaryButton: .snoozeButton, secondaryButtonBehavior: .countdown ) struct EmptyMetadata: AlarmMetadata {} let config = AlarmManager.AlarmConfiguration( countdownDuration: nil, schedule: schedule, attributes: AlarmAttributes( presentation: AlarmPresentation(alert: alert), metadata: EmptyMetadata(), tintColor: .blue ), sound: .default ) let alarm = try await AlarmManager.shared.schedule(id: id, configuration: config)
Repeating Alarm
Use
.weekly(Array(weekdays)) for specific days:
let time = Alarm.Schedule.Relative.Time(hour: 6, minute: 0) let schedule = Alarm.Schedule.relative(.init( time: time, repeats: .weekly([.monday, .tuesday, .wednesday, .thursday, .friday]) ))
Countdown Timer
Set
schedule: nil and provide countdownDuration with a preAlert interval:
let countdown = Alarm.CountdownDuration( preAlert: 300, // 5 minutes postAlert: 10 // Optional post-alert snooze window ) let config = AlarmManager.AlarmConfiguration( countdownDuration: countdown, schedule: nil, attributes: attributes, sound: .default )
Timers support pause/resume and show a countdown presentation when
AlarmPresentation.countdown is provided.
Snooze Configuration
Snooze uses
CountdownDuration.postAlert combined with a .snoozeButton secondary action:
let alert = AlarmPresentation.Alert( title: "Alarm", stopButton: .stopButton, secondaryButton: .snoozeButton, secondaryButtonBehavior: .countdown // Starts post-alert countdown ) let countdownDuration = Alarm.CountdownDuration( preAlert: nil, postAlert: 9 * 60 // 9-minute snooze )
Part 4: Customizing Alarm UI
Alert Presentation
The alert state is shown when the alarm fires. The stop button is required; secondary button is optional.
// Minimal let basic = AlarmPresentation.Alert( title: "Alarm", stopButton: .stopButton ) // With custom button labels let custom = AlarmPresentation.Alert( title: "Medication Reminder", stopButton: AlarmButton(label: "Taken"), secondaryButton: AlarmButton(label: "Remind Later"), secondaryButtonBehavior: .countdown ) // With open-app action let openApp = AlarmPresentation.Alert( title: "Workout Time", stopButton: .stopButton, secondaryButton: .openAppButton, secondaryButtonBehavior: .custom )
Countdown Presentation
Shown while a timer counts down. Only relevant for alarms with
countdownDuration.preAlert.
let countdown = AlarmPresentation.Countdown( title: "Timer Running", pauseButton: .pauseButton )
Paused Presentation
Shown when a countdown timer is paused.
let paused = AlarmPresentation.Paused( title: "Timer Paused", resumeButton: .resumeButton )
Full Three-State Presentation
Combine all three for a complete timer experience:
let presentation = AlarmPresentation( alert: AlarmPresentation.Alert( title: "Timer Complete", stopButton: .stopButton, secondaryButton: .repeatButton, secondaryButtonBehavior: .countdown ), countdown: AlarmPresentation.Countdown( title: "Cooking Timer", pauseButton: .pauseButton ), paused: AlarmPresentation.Paused( title: "Timer Paused", resumeButton: .resumeButton ) )
Part 5: Managing Alarms
Retrieve All Alarms
let alarms = try AlarmManager.shared.alarms
Pause / Resume
try await AlarmManager.shared.pause(id: alarmID) try await AlarmManager.shared.resume(id: alarmID)
Cancel
try await AlarmManager.shared.cancel(id: alarmID)
Observe Alarm Updates
Use
alarmUpdates to keep UI in sync. An alarm absent from the emitted array is no longer scheduled.
for await alarms in AlarmManager.shared.alarmUpdates { self.alarms = alarms }
Part 6: Live Activity Integration
AlarmKit alarms appear in the Dynamic Island and Lock Screen through
ActivityConfiguration. Add a Widget Extension target and implement the widget using AlarmAttributes.
struct AlarmWidgetView: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: AlarmAttributes<YourMetadata>.self) { context in // Lock Screen presentation VStack { Text(context.attributes.presentation.alert.title) if context.state.mode == .countdown { Text( timerInterval: context.state.countdownEndDate .timeIntervalSinceNow, countsDown: true ) .bold() } } .padding() } dynamicIsland: { context in DynamicIsland { DynamicIslandExpandedRegion(.leading) { Text(context.attributes.presentation.alert.title) } DynamicIslandExpandedRegion(.trailing) { if context.state.mode == .countdown { Text( timerInterval: context.state.countdownEndDate .timeIntervalSinceNow, countsDown: true ) } } } compactLeading: { Image(systemName: "alarm") } compactTrailing: { if context.state.mode == .countdown { Text( timerInterval: context.state.countdownEndDate .timeIntervalSinceNow, countsDown: true ) } } minimal: { Image(systemName: "alarm") } } } }
Part 7: SwiftUI Integration
ViewModel Pattern with @Observable
import AlarmKit @Observable class AlarmViewModel { var alarms: [Alarm] = [] private let manager = AlarmManager.shared func requestAuthorization() { Task { _ = try? await manager.requestAuthorization() } } func loadAndObserve() { Task { alarms = (try? manager.alarms) ?? [] for await updated in manager.alarmUpdates { alarms = updated } } } func addAlarm(hour: Int, minute: Int, weekdays: Set<Locale.Weekday>) { Task { let time = Alarm.Schedule.Relative.Time(hour: hour, minute: minute) let schedule = Alarm.Schedule.relative(.init( time: time, repeats: weekdays.isEmpty ? .never : .weekly(Array(weekdays)) )) let alert = AlarmPresentation.Alert( title: "Alarm", stopButton: .stopButton, secondaryButton: .snoozeButton, secondaryButtonBehavior: .countdown ) struct EmptyMetadata: AlarmMetadata {} let config = AlarmManager.AlarmConfiguration( countdownDuration: Alarm.CountdownDuration( preAlert: nil, postAlert: 9 * 60 ), schedule: schedule, attributes: AlarmAttributes( presentation: AlarmPresentation(alert: alert), metadata: EmptyMetadata(), tintColor: .blue ), sound: .default ) _ = try? await manager.schedule(id: UUID(), configuration: config) } } func cancel(id: UUID) { Task { try? await manager.cancel(id: id) } } func togglePause(id: UUID, isPaused: Bool) { Task { if isPaused { try? await manager.resume(id: id) } else { try? await manager.pause(id: id) } } } }
Alarm List View
struct AlarmListView: View { @State private var viewModel = AlarmViewModel() var body: some View { NavigationStack { List(viewModel.alarms, id: \.id) { alarm in AlarmRow(alarm: alarm, viewModel: viewModel) } .navigationTitle("Alarms") .onAppear { viewModel.requestAuthorization() viewModel.loadAndObserve() } } } }
Part 8: Best Practices
| Practice | Detail |
|---|---|
| Request authorization early | On first launch or first alarm creation attempt |
| Handle denial gracefully | Guide users to Settings if permission was denied |
| Persist alarm UUIDs | Store IDs to manage alarms across app launches |
| Implement widget extension | Required for countdown/Dynamic Island presentation |
Use | Keep UI in sync; don't poll or cache stale state |
| Test on physical device | Alarm sounds, notifications, and Live Activities require real hardware |
| Respect system limits | There is a system-imposed cap on alarms per app |
Use | Not -- the correct property name is |
Resources
WWDC: 2025-230
Docs: /alarmkit, /alarmkit/alarmmanager, /alarmkit/alarm, /alarmkit/alarmpresentation, /alarmkit/alarmattributes
Skills: axiom-extensions-widgets-ref, axiom-swiftui-26-ref