In macOS 10.14 users can choose to adopt a system-wide light or dark appearance and I need to adjust some colours manually depend of the current mode.
6 Answers
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).

- 90,870
- 19
- 190
- 224
-
It may not always possible to call this method from a view. – Muntashir Akon Jan 09 '19 at 11:19
-
5You can use `NSApp.mainWindow.effectiveAppearance` – 93sauu Jan 09 '19 at 11:56
-
1@MuntashirAkon: You don't need to call it "from" a view, that is irrelevant. You just need _a_ view that determines the context, like the main view of your app as a fallback. – DarkDust Jan 09 '19 at 14:27
-
@SaúlMorenoAbril, it works. When running from another thread I got an issue saying: “-`[NSView effectiveAppearance]` must be used from main thread only.” which is quite expected but doesn't do any harm by the way. – Muntashir Akon Jan 10 '19 at 03:18
-
1Do you have to call it from ```drawRect```? What about ```viewDidLayout```? – Supertecnoboff Mar 20 '20 at 10:40
-
@Supertecnoboff: You have to call it from wherever you wish to know whether the effective appearance is dark mode or not. Note that an appearance is a property of a view and can be different within the same view hierarchy, so do something like `appearanceIsDark(someView.effectiveAppearance)`. – DarkDust Mar 20 '20 at 11:10
-
1This code used to work for me. But it mysteriously does not work anymore, after updating Xcode to 11.4. It returns Aqua mode both in case of light and dark mode. Does anyone else has the same problem? – willy Apr 02 '20 at 08:10
-
@michalis: The problem was that I was using Qt 5.14 (sorry that I did not mention that) to build my project. And they had a bug (QTBUG-83111), which they recently fixed. – willy Apr 07 '20 at 12:29
-
1@93sauu There seem to be race conditions doing that. NSApp.effectiveAppearance seems to be a better way to detect the system-wide setting. – Paul Sanders Oct 29 '20 at 16:49
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

- 3,770
- 3
- 27
- 43
-
4The best way to react to an appearance change is `-[NSView viewDidChangeEffectiveAppearance]`. You can also KVO the `effectiveAppearance` property of a view, for example if you want to react to an appearance change in a view controller. Remember that the appearance of a view may be different than the "current" or system appearance. – DarkDust Sep 19 '18 at 13:08
-
1`NSAppearance.currentAppearance` doesn't always work when the user is switching between Light and Dark mode or vice versa. – Muntashir Akon Jan 09 '19 at 11:14
-
2`NSAppearance.currentAppearance` returns the object's appearance that is active on the current thread so you cannot be sure because maybe the current object has `Aqua` or `Dark Aqua` assigned in place of `Inherited`. So the best solution if use `someView.effectiveAppearance`. – 93sauu Jan 09 '19 at 11:47
-
1```AppleInterfaceThemeChangedNotification``` didn't work for me. Instead use ```viewDidLayout``` to call your dark/light mode method(s). – Supertecnoboff Mar 20 '20 at 10:39
Swift 4
func isDarkMode(view: NSView) -> Bool {
if #available(OSX 10.14, *) {
return view.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
return false
}
-
1I would recommend to get rid of that optional: in a lot of circumstances `NSAppearance.currentAppearance` may give a wrong result (for example, if you call it outside the methods [mentioned in this answer](https://stackoverflow.com/a/51672221/400056) and the user changes the system appearance, `NSAppearance.currentAppearance` may still return the old system appearance). So always ask a specific view about its effective appearance. – DarkDust Jan 09 '19 at 14:31
-
2Also, the check for `appearance.name == .darkAqua` may also be wrong in several circumstances as the _actual_ appearance is a "compound". This is why [`bestMatch(from:)`](https://developer.apple.com/documentation/appkit/nsappearance/2980972-bestmatch) exists and should be used instead. – DarkDust Jan 09 '19 at 14:33
-
1
For me neither of these answers worked, if I wanted a global state, not per view, and I didn't have access to the view, and I wanted to be notified for updates.
The solution was to ask for NSApp.effectiveAppearance
in the main thread, or at least after the current callback method has returned to the system.
So, first I have to register, following the directions of Saúl Moreno Abril, with a code like
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
then on the callback method write something like
-(void)themeChanged:(NSNotification *) notification {
[self performSelectorOnMainThread:@selector(themeChangedOnMainThread) withObject:nil waitUntilDone:false];
}
and then the actual code:
- (void) themeChangedOnMainThread {
NSAppearance* appearance = NSApp.effectiveAppearance;
NSString* name = appearance.name;
BOOL dark = [appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] == NSAppearanceNameDarkAqua;
}
Also the answer from Borzh helped, but is seemed more fragile than the others.

- 1,792
- 23
- 32
There are actually 8 possible appearances for a view, and 4 of them are for ordinary use. That is,
NSAppearanceNameAqua
the Light Mode,NSAppearanceNameDarkAqua
the Dark Mode,NSAppearanceNameAccessibilityHighContrastAqua
Light Mode with increased contrast (set from Accessibility),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.

- 381
- 4
- 4
To know if the app appearance is Dark, use next code:
+ (BOOL)isDarkMode {
NSString *interfaceStyle = [NSUserDefaults.standardUserDefaults valueForKey:@"AppleInterfaceStyle"];
return [interfaceStyle isEqualToString:@"Dark"];
}

- 5,069
- 2
- 48
- 64
-
`NSUserDefaults.standardUserDefaults` might not be as the same as the app's view. – L.-T. Chen Jul 30 '19 at 20:05