Components / Navigation

Radial menu

A floating action menu with a rotating trigger, staggered spring entrances, and an arc of reachable actions.

iOS 17+Swift 6MITView source
Radial menu, rendered by SwiftUI on an iPhone simulator
Recorded in the iOS simulator

Real SwiftUI, captured on an iPhone 17 Pro simulator. Run the native gallery

Installation

Install the native Swift CLI, then add the component to your app.

Terminal
hugsy add radial-menu

Prefer to copy it yourself? Open the complete Swift file and add it to your iOS app target in Xcode. No package dependency is required.

Usage

SwiftUI
@State private var expanded = false

HugsyRadialMenu(actions: [
  .init(id: "photo", title: "Add photo", symbol: "camera") { addPhoto() },
  .init(id: "note", title: "New note", symbol: "square.and.pencil") { newNote() },
  .init(id: "link", title: "Save link", symbol: "link") { saveLink() }
], isExpanded: $expanded)

Supply stable, unique action IDs and main-actor closures. Use three or four actions to keep targets separated. Reserve the component's full width and height in the layout. Actions close the menu before running their closures.

API reference

Radial menu parameters
ParameterType & behaviorDefault
actions[HugsyRadialAction]

Stable IDs, labels, SF Symbols, and main-actor closures.

Required
isExpandedBinding<Bool>

Controls the action arc. Selection closes it before running an action.

Required
radiusCGFloat

Arc radius in points, clamped to at least 70. Reserve sufficient layout space.

100

Interaction

Open the action menu, then choose an action. The actions spring outward in sequence and collapse after selection.

CollapsedOpeningExpandedSelectedClosing

Accessibility

Every action is a labeled native button. Collapsed actions are removed from hit testing and accessibility. The trigger announces expanded or collapsed state. Reduce Motion removes travel interpolation and trigger rotation.

Test with VoiceOver, Dynamic Type, and Reduce Motion on a physical device before shipping. The gallery demonstrates the interaction, not every application-specific content state.