macOS Window Levels Explained: NSWindowLevel Deep Dive
Quick answer
macOS uses a numeric window level system to determine which windows appear above others. Every window has an NSWindowLevel value (an alias for CGWindowLevel, which is just an Int32). Normal app windows sit at level 0 (NSNormalWindowLevel). Floating panels sit at level 3. The menu bar, screen savers, and system overlays each occupy progressively higher levels. A window at level 5 always renders above a window at level 3, regardless of which app is focused.
This is the mechanism that lets certain apps keep windows "always on top." Tools like Noticky use a window level above the standard desktop layer to keep sticky notes visible above everything, including fullscreen apps. Understanding window levels is essential for any macOS developer building overlay UIs, floating panels, HUDs, or diagnostic tools.
What is NSWindowLevel?
NSWindowLevel is a type alias defined in AppKit:
public typealias NSWindowLevel = IntUnder the hood, it maps directly to CGWindowLevel, a Core Graphics type:
public typealias CGWindowLevel = Int32Apple defines several constants that represent predefined levels in the window stacking order. These are not arbitrary numbers. They correspond to specific layers in the macOS window server (called WindowServer), the system process responsible for compositing every pixel on your display.
When the window server renders a frame, it draws windows bottom-up: lowest level first, highest level last. Within the same level, windows are ordered by their position in the window list (most recently focused on top). But a window at a higher level always paints over a window at a lower level, no matter the focus state.
The macOS window level hierarchy
Here is the complete hierarchy of predefined NSWindowLevel constants, from lowest to highest:
| Constant | Raw Value | Purpose |
|---|---|---|
CGWindowLevelForKey(.baseWindow) | -2147483648 (Int32.min) | Desktop wallpaper, Finder desktop icons |
CGWindowLevelForKey(.minimumWindow) | -2147483643 | Lowest usable window level |
NSNormalWindowLevel | 0 | Standard app windows (Safari, VS Code, Finder) |
NSFloatingWindowLevel | 3 | Floating palettes, inspectors, tool panels |
NSSubmenuWindowLevel | 3 | Submenus (same as floating) |
NSTornOffMenuWindowLevel | 3 | Torn-off menus (legacy) |
NSModalPanelWindowLevel | 8 | Modal dialogs, sheets |
NSMainMenuWindowLevel | 24 | The main menu bar |
NSStatusWindowLevel | 25 | Status bar items, system-level overlays |
NSDockWindowLevel | deprecated | The Dock (no longer publicly accessible) |
NSPopUpMenuWindowLevel | 101 | Pop-up menus, context menus |
NSScreenSaverWindowLevel | 1000 | Screen saver overlay |
CGWindowLevelForKey(.maximumWindow) | 2147483631 | Highest usable window level |
Source: Apple Developer Documentation: NSWindow.Level
Key observations
Levels are sparse, not contiguous. The jump from NSNormalWindowLevel (0) to NSFloatingWindowLevel (3) is only 3. But the jump from NSStatusWindowLevel (25) to NSPopUpMenuWindowLevel (101) is 76. Apple intentionally leaves gaps so developers can insert custom levels between the predefined ones.
Some constants share the same value. NSFloatingWindowLevel, NSSubmenuWindowLevel, and NSTornOffMenuWindowLevel are all level 3. This means a floating palette and a submenu compete for stacking order based on focus, not level.
The range is massive. CGWindowLevel is an Int32, so valid levels span from about -2.1 billion to +2.1 billion. In practice, you should stay within the predefined constants or use small offsets from them (e.g., NSFloatingWindowLevel + 1).
How the window server composites levels
The macOS WindowServer process handles all window compositing. It runs outside your app's process space and has final authority over what appears on screen. Here is the rendering pipeline, simplified:
- Collect window list. WindowServer maintains an ordered list of every window from every app, tagged with its level and Space membership.
- Sort by level, then by order within level. Lower levels render first (background). Within the same level, the most recently activated window renders last (on top).
- Apply Space filtering. Windows are filtered by their Space assignment. A window in Space 1 does not render in Space 2, unless the window is flagged as appearing on all Spaces (
NSWindow.CollectionBehavior.canJoinAllSpaces). - Composite. The final frame is composited using the GPU, with transparency, shadows, and blur effects applied per-window.
This is why simply raising a window's level to NSFloatingWindowLevel keeps it above normal windows but does not make it visible in fullscreen. Fullscreen mode creates a separate Space, and Space filtering happens after level sorting.
CGWindowLevel vs. NSWindowLevel
You will encounter both terms in Apple's documentation. Here is the relationship:
- `CGWindowLevel` is the Core Graphics (C-level) type. It is an
Int32. Core Graphics provides the functionCGWindowLevelForKey(_:)to convert aCGWindowLevelKeyenum case into a concrete numeric level. - `NSWindowLevel` is the AppKit (Objective-C/Swift) type. It is a type alias for
Int. AppKit wraps the Core Graphics constants with friendlier names likeNSWindow.Level.floating.
In Swift, you set a window's level like this:
window.level = .floating // NSFloatingWindowLevel (3)
window.level = .statusBar // NSStatusWindowLevel (25)
window.level = NSWindow.Level(rawValue: 101) // Pop-up menu level
window.level = NSWindow.Level(rawValue: NSWindow.Level.floating.rawValue + 1) // Custom: just above floatingIn Objective-C:
[window setLevel:NSFloatingWindowLevel];
[window setLevel:NSStatusWindowLevel];
[window setLevel:NSFloatingWindowLevel + 1]; // Custom offsetBoth ultimately call through to the window server, which only cares about the numeric value.
Fullscreen, Spaces, and the compositing boundary
This is where most developers get tripped up. Setting a high window level does not guarantee visibility in fullscreen. Here is why.
When an app enters fullscreen (NSWindow.toggleFullScreen(_:)), macOS:
- Creates a new Space dedicated to that window.
- Animates the window into the new Space.
- Hides all other windows that belong to other Spaces.
The crucial point: Space filtering is independent of window levels. A window at level 1000 (NSScreenSaverWindowLevel) in Space 1 is invisible in the fullscreen Space, because it does not belong to that Space.
To appear across all Spaces, including fullscreen, a window must use the canJoinAllSpaces collection behavior:
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]Combined with an elevated window level, this makes the window visible above fullscreen apps. This is the technique that apps like Noticky use to keep notes floating above everything, including fullscreen VS Code, Figma, or Zoom.
Good to know:
canJoinAllSpacesalone (without an elevated level) makes a window appear on every Space, but it will still render behind fullscreen content. You need both: the collection behavior for Space membership, and the elevated level for stacking order.
Practical use cases for custom window levels
Floating reference panels
Developer tools like Xcode's inspector panels use NSFloatingWindowLevel to stay above editor windows. Third-party apps like color pickers, timers, and calculators often do the same.
Always-on-top sticky notes
Sticky note apps that need to stay visible during focused work require a level above NSFloatingWindowLevel. Noticky uses this approach combined with canJoinAllSpaces to persist across fullscreen Spaces. This is the technical foundation of its Always on Top feature.
Screen recording overlays
Apps like screen recorders and streaming tools use NSStatusWindowLevel or higher to draw recording indicators, watermarks, or camera previews above all other content.
System-level HUDs
Volume and brightness overlays from macOS itself use levels near NSStatusWindowLevel. Third-party HUD replacements must match or exceed this level.
Screen savers and lock screens
NSScreenSaverWindowLevel (1000) is reserved for the screen saver. The login window operates above even this level. You should never set your app's windows to this level in production unless you are building a screen saver replacement.
Common pitfalls
Setting levels too high
Using CGWindowLevelForKey(.maximumWindow) or similarly extreme values can cause your window to render above system UI elements like the menu bar, the Dock, and even the Force Quit dialog. This makes your app feel broken and can trap users. Stick to the predefined constants or use small offsets.
Forgetting about mouse event passthrough
A high-level window captures all mouse events in its frame, blocking interaction with windows below it. If your overlay is decorative (e.g., a crosshair or watermark), set window.ignoresMouseEvents = true to allow clicks to pass through.
Ignoring accessibility
Screen readers like VoiceOver traverse windows in level order. A high-level window that is not properly labeled with accessibility attributes will confuse VoiceOver users. Always set accessibilityRole and accessibilityLabel on overlay windows.
Level conflicts between apps
Two apps using the same custom level will fight for stacking order based on focus. There is no system-wide coordination of custom levels. If your floating panel keeps getting hidden by another app's floating panel, one of you is using a level that conflicts.
Inspecting window levels with Accessibility Inspector
You can inspect any window's current level using Apple's Accessibility Inspector (included with Xcode) or with a quick Terminal command:
# List all windows with their levels, owners, and names
# Requires Accessibility permissions in System Settings > Privacy & Security
python3 -c "
import Quartz
windows = Quartz.CGWindowListCopyWindowInfo(
Quartz.kCGWindowListOptionAll,
Quartz.kCGNullWindowID
)
for w in windows:
name = w.get('kCGWindowName', '(unnamed)')
owner = w.get('kCGWindowOwnerName', '?')
level = w.get('kCGWindowLayer', '?')
print(f'{owner:30s} | level {level:>5} | {name}')
"This prints every window on your system with its owner app, level, and name. You will see the Dock, menu bar, Spotlight, notification center, and every app window, each at their respective levels. It is the fastest way to understand how macOS stacks your display.
How Noticky uses window levels
Noticky is a macOS menu bar sticky notes app built specifically around the window level system. Each sticky note is a native NSWindow set to a level that renders above standard and floating windows, combined with canJoinAllSpaces and fullScreenAuxiliary collection behaviors.
This means your notes stay visible when you:
- Switch between regular app windows
- Move between Spaces via Mission Control
- Enter fullscreen mode in any app
- Use Stage Manager on macOS Ventura and later
The result is a sticky note that behaves the way sticky notes should have always worked on macOS: persistent, always visible, and never lost behind your work. You create a note with Command-Shift-N, position it anywhere, and it stays there. $6 one-time, no subscription.
For a detailed walkthrough of keeping notes visible in fullscreen, see How to Keep a Sticky Note Visible in Fullscreen on Mac.
FAQ
What is the default window level for macOS apps?
The default window level for all standard macOS app windows is NSNormalWindowLevel, which has a raw value of 0. This includes document windows, preference panels, and any window created with NSWindow() or loaded from a storyboard. Windows at this level follow normal stacking behavior based on focus order.
Can I set any arbitrary integer as a window level?
Yes. NSWindow.Level accepts any Int value, and CGWindowLevel accepts any Int32. However, using values outside the predefined constants is risky. Apple does not document the levels used by system UI (Dock, menu bar, Notification Center), so you may inadvertently render above or below critical system elements. Stick to the predefined constants or use small offsets like NSWindow.Level.floating.rawValue + 1.
Why does my floating window disappear in fullscreen?
Because fullscreen mode creates a separate Space, and your window belongs to a different Space. Window levels control stacking order within a Space, not across Spaces. To appear in the fullscreen Space, your window needs canJoinAllSpaces in its collectionBehavior. See the section on fullscreen and Spaces above.
Is there a way to keep any window always on top on macOS?
macOS has no built-in "always on top" toggle. Third-party tools like BetterTouchTool and Rectangle Pro can raise a window's level to floating, but this only works on the regular desktop. For sticky notes that stay on top even in fullscreen, Noticky is purpose-built for this with a $6 one-time purchase.
Do window levels affect energy consumption?
Minimally. The window server composites all visible windows every frame regardless of level. A window at level 25 costs the same GPU cycles as one at level 0. What matters is the window's content complexity (blur, transparency, animation), not its level value.
Get Noticky — $6
A native macOS sticky note that stays visible in fullscreen. One-time purchase, no subscription.
⬇ Download — $6macOS 15 Sequoia+ · < 5MB · Secure checkout
Related articles
Keep a Chrome Window Always on Top on Mac (5 Ways)
macOS has no native way to keep a Chrome window always on top. Here are 5 methods that work — from PiP mode to Noticky's floating notes.
Floating Notes on Mac: How to Keep Notes Above Every Window
Floating notes on Mac stay visible above all windows, even fullscreen apps. Learn what they are, why they matter, and how to set them up with Noticky.
Pin Window on Top on Mac: Keyboard Shortcuts Guide
Learn how to pin a window on top on macOS using keyboard shortcuts. Compare BetterTouchTool, Rectangle Pro, Hammerspoon, and Noticky for always-on-top.