← Blog
10 min read

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 = Int

Under the hood, it maps directly to CGWindowLevel, a Core Graphics type:

public typealias CGWindowLevel = Int32

Apple 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:

ConstantRaw ValuePurpose
CGWindowLevelForKey(.baseWindow)-2147483648 (Int32.min)Desktop wallpaper, Finder desktop icons
CGWindowLevelForKey(.minimumWindow)-2147483643Lowest usable window level
NSNormalWindowLevel0Standard app windows (Safari, VS Code, Finder)
NSFloatingWindowLevel3Floating palettes, inspectors, tool panels
NSSubmenuWindowLevel3Submenus (same as floating)
NSTornOffMenuWindowLevel3Torn-off menus (legacy)
NSModalPanelWindowLevel8Modal dialogs, sheets
NSMainMenuWindowLevel24The main menu bar
NSStatusWindowLevel25Status bar items, system-level overlays
NSDockWindowLeveldeprecatedThe Dock (no longer publicly accessible)
NSPopUpMenuWindowLevel101Pop-up menus, context menus
NSScreenSaverWindowLevel1000Screen saver overlay
CGWindowLevelForKey(.maximumWindow)2147483631Highest 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:

  1. Collect window list. WindowServer maintains an ordered list of every window from every app, tagged with its level and Space membership.
  2. 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).
  3. 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).
  4. 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:

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 floating

In Objective-C:

[window setLevel:NSFloatingWindowLevel];
[window setLevel:NSStatusWindowLevel];
[window setLevel:NSFloatingWindowLevel + 1]; // Custom offset

Both 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:

  1. Creates a new Space dedicated to that window.
  2. Animates the window into the new Space.
  3. 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: canJoinAllSpaces alone (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:

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 — $6

macOS 15 Sequoia+ · < 5MB · Secure checkout

Related articles