How can dark mode be detected on macOS 10.14?

Since the actual appearance object you usually get via effectiveAppearance is a composite appearance, asking for its name directly probably isn't a reliable solution.

Asking for the currentAppearance usually isn't a good idea, either, as a view may be explicitly set to light mode or you want to know whether a view is light or dark outside of a drawRect: where you might get incorrect results after a mode switch.

The solution I came up with looks like this:

BOOL appearanceIsDark(NSAppearance * appearance)
{
    if (@available(macOS 10.14, *)) {
        NSAppearanceName basicAppearance = [appearance bestMatchFromAppearancesWithNames:@[
            NSAppearanceNameAqua,
            NSAppearanceNameDarkAqua
        ]];
        return [basicAppearance isEqualToString:NSAppearanceNameDarkAqua];
    } else {
        return NO;
    }
}

You would use it like appearanceIsDark(someView.effectiveAppearance) since the appearance of a specific view may be different than that of another view if you explicitly set someView.appearance.

You could also create a category on NSAppearance and add a - (BOOL)isDark method to get someView.effectiveAppearance.isDark (better chose a name that is unlikely to be used by Apple in the future, e.g. by adding a vendor prefix).


Swift 4

func isDarkMode(view: NSView) -> Bool {
    if #available(OSX 10.14, *) {
        return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
    }
    return false
}

I have used the current appearance checking if the system is 10.14

+ (BOOL)isDarkMode {
    NSAppearance *appearance = NSAppearance.currentAppearance;
    if (@available(*, macOS 10.14)) {
        return appearance.name == NSAppearanceNameDarkAqua;
    }

    return NO;
}

And to detect the change of mode in a view the methods are:

- (void)updateLayer;
- (void)drawRect:(NSRect)dirtyRect;
- (void)layout;
- (void)updateConstraints;

And to detect the change of mode in a view controller the methods are:

- (void)updateViewConstraints;
- (void)viewWillLayout;
- (void)viewDidLayout;

Using notification:

// Monitor menu/dock theme changes...
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];

-(void)themeChanged:(NSNotification *) notification {
    NSLog (@"%@", notification);
}

For more information Dark Mode Documentation


There are actually 8 possible appearances for a view, and 4 of them are for ordinary use. That is,

  1. NSAppearanceNameAqua the Light Mode,
  2. NSAppearanceNameDarkAqua the Dark Mode,
  3. NSAppearanceNameAccessibilityHighContrastAqua Light Mode with increased contrast (set from Accessibility),
  4. NSAppearanceNameAccessibilityHighContrastDarkAqua Dark Mode with increased contrast.

A direct comparison

appearance.name == NSAppearanceNameDarkAqua;

may fail to detect the dark mode if it is with increased contrast. So, always use bestMatchFromAppearancesWithNames instead.

And it is even better to take account of the high-contrast appearances for better accessibility.