Community additions to Apple's Human Interface Guidelines
Apple's Human Interface Guidelines are amazingly well done. However, there are many details they don't clearly specify. This is an attempt to document the best conventions and patterns in the community.
When you create a new macOS app project in Xcode, it includes a main menu with a lot of items. You might be tempted to remove stuff you don't need. For example, I have seen many apps remove “Undo” and “Redo” because their app doesn't support that. This is usually because they don't realize how the responder chain works. I made this mistake myself.
The reason you should not remove such menu items is that you cannot foresee what will need it. For example, if your app has a way to save a file, the filename text field in the save panel supports undo/redo and the “Undo” menu item works when the text field is key (focused). Another example, my Black Out app has a “Quick Action” extension, and if the user runs it from Finder, the extension is able to use Finder's “Undo” and “Redo” menu items.
So what default menu items can be removed?
- The
Settingsmenu item in the app menu. - All menu items in the
Filemenu except forClose. Findand the below menu items in theEditmenu if your app doesn't include text editing.- The
Formatmenu. - The toolbar and sidebar menu items in the
Viewmenu if your app doesn't need those.
- Use
Formwith.formStyle(.grouped). Group controls withSection. - The window should not be translucent.
- The minimize and maximize traffic light buttons should be disabled, but not removed.
- The “General” tab should use the
gearshapesymbol (notgear). - The “Advanced” tab should use the
gearshape.2symbol. - You should not have license or update functionality there. Nor should you have an "About" tab. It's a settings window.
- The window should not close when the user presses the Escape key. Escape is for dismissing transient UI like dialogs, sheets, and panels, not regular windows. Apple's own apps are inconsistent about this, but treating it as a normal window is correct. SwiftUI's
Settingsscene gives you this behavior by default. - The window should show up in the "Window" menu. This lets users find and bring the settings window to the front when it gets buried behind other windows. Apple's own apps are inconsistent about this, but since it's a regular window (not a panel), it should be listed.
If your app has a drag and drop target for files, don't forget to also make it possible to click the drop area to open files through an open panel instead of dragging. Alternatively, add an “Open” button inside the drop target.
If a user drags a mix of supported and unsupported files (for example, both JPEG and PNG files onto an app that only accepts JPEG), the app should accept the valid items and silently ignore the rest. Do not reject the entire drop or show a blocking error dialog. Drag and drop is a low-friction interaction and should not be interrupted.
SwiftUI's dropDestination(for:) and onDrop(of:) handle this automatically by filtering to only the matching types. When none of the dragged items are valid, these APIs will show the “not allowed” cursor and reject the drop entirely, which is the correct behavior.
If some files were skipped and that might confuse the user, consider showing a brief non-blocking notification after the drop (for example, “3 of 5 files imported”). Mention accepted file types in the drop zone label to help prevent confusion upfront.
You might want to let the user set your app's window to always be on top. You can find this behavior in the Simulator app.
The menu bar item that toggles this functionality should be named “Stay on Top” and should be in the “Window” menu below “Enter Full Screen”, or if it's not there, below “Tile Window to Right of Screen”.
The setting should be persisted in UserDefaults.
When enabled, the window should be set to window.level = .floating.
If your app needs to access the internet for any reason, I would strongly recommend including a Internet Access Policy. This lets firewall apps present to the user what and why your app needs access to. It also makes it more likely the user will grant access.
If your app has a text field where the user can set a number, for example, for the width of something, make it convenient to increase/decrease the number.
- Place a stepper control on the right side of the text field.
- When the text field is focused, make it possible to use arrow up/down keys change the number by 1.
- If the user also holds down the Option key, change the number by 10.
- If the user reaches a lower or upper limit, shake the text field contents to indicate that.
You can find a real-world example of this in my Gifski app (missing the stepper though) (source).
The most common conventions I have seen for a settings sheet dismissal button is either a “Done” button on the right side (primary position) of the navigation bar or an “X” icon on the left side (navigational position).
There are several benefits to using a “Done” button on the right side:
- Apple does that in most apps. You cannot go wrong following what Apple does.
- “Done” has a larger tap target.
- Friendlier. An “X” might make people think the settings will not be saved.
- Easier to reach for right-handed people, the majority.
- An informal Twitter poll tells us that most users prefer this.
What you should definitely not do:
- “Done” button on the left side.
- “Cancel” button on either side.
- “Dismiss” button on either side.
Click here to see how you correctly do a settings screen in SwiftUI.
struct SettingsScreen: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
Form {
Section("Unicorn") {
// …
}
}
.navigationTitle("Settings")
.toolbarTitleDisplayMode(.inline)
.toolbar {
// Note that it's `.confirmationAction` and not `.primaryAction`.
ToolbarItem(placement: .confirmationAction) {
Button(role: .confirm) {
dismiss()
}
}
}
}
}
}People seem undecided on whether to use a normal or large navigation bar title for a settings sheet.
If you want to include a undo button for some settings control on iOS, prefer arrow.uturn.backward. It's what most people prefer.
Nothing yet
Nothing yet

