SF Symbols in AppKit (NSImage): a practical guide

SF Symbols aren’t just an iOS thing. macOS has had them since Big Sur, and AppKit’s entry point carries the same design — and the same liability — as the UIKit one:

let image = NSImage(
    systemSymbolName: "externaldrive.badge.plus",
    accessibilityDescription: "Add drive"
)

systemSymbolName: is a String. NSImage(systemSymbolName:accessibilityDescription:) is failable — it returns NSImage?, nil when the name doesn’t resolve. Exactly like UIKit, the type system can’t tell a real symbol name from a typo, so the failure is invisible until runtime, on macOS, where blank toolbar items are especially easy to miss.

AppKit makes the maintenance problem slightly worse

Two AppKit-specific wrinkles compound the stringly-typed cost:

1. The accessibility description is mandatory and also hand-written. Every call site has two hand-authored strings — the symbol name and the a11y label. Miss the symbol name and you get a blank image; the a11y string is fine but pointing at nothing.

2. macOS UIs are toolbar- and menu-heavy. Toolbars, menu items, status-bar items, outline view cells — macOS apps tend to have a high density of symbol references per screen. More call sites means more hand-typed strings means more surface area for the silent-typo failure.

// A typical macOS toolbar — every symbol a hand-typed string
toolbarItem.image = NSImage(systemSymbolName: "sidebar.left", accessibilityDescription: "Toggle Sidebar")
addButton.image   = NSImage(systemSymbolName: "plus", accessibilityDescription: "Add")
trashButton.image = NSImage(systemSymbolName: "trash", accessibilityDescription: "Delete")

Typed NSImage symbols

SFSymbolsKit’s NSImage extension gives AppKit the same compile-checked accessors as UIKit, and synthesises a sensible accessibility description from the symbol name so you can’t forget it:

import AppKit
import SFSymbolsKit

toolbarItem.image = NSImage.SFSymbols.sidebarLeft
addButton.image   = NSImage.SFSymbols.plus
trashButton.image = NSImage.SFSymbols.trash

Need a specific accessibility label or symbol configuration? Compose the typed name instead of hand-writing the symbol string:

let image = NSImage(
    systemSymbolName: String.SFSymbols.externaldriveBadgePlus,
    accessibilityDescription: "Add external drive"
)

The symbol identity is now compiler-checked; the accessibility string stays under your control. Autocomplete lists every symbol, refactors are safe, and a misspelling is a build error rather than an empty toolbar button a Mac user quietly works around.

FAQ

Does this support NSImage.SymbolConfiguration? Yes — apply configuration the same way you would today; pass String.SFSymbols.<name> as the name instead of a literal.

Catalyst / cross-platform code? In a shared file, the UIImage extension applies under UIKit and the NSImage extension under AppKit — the typed String.SFSymbols.* names work everywhere and let you branch by platform without duplicating raw strings.

What about menu items and status items? Anywhere AppKit takes a system symbol name string, the typed property substitutes directly — menus, status bar, outline/table cells included.


This completes the framework set: SwiftUI, UIKit, and now AppKit — one stringly-typed problem, three Apple frameworks, one typed fix. The tutorial covers all three end to end.