Skip to content

Match the macOS app appearance to the selected theme - #58902

Merged
Veykril merged 5 commits into
zed-industries:mainfrom
huacnlee:add-set-appearance
Jul 28, 2026
Merged

Match the macOS app appearance to the selected theme#58902
Veykril merged 5 commits into
zed-industries:mainfrom
huacnlee:add-set-appearance

Conversation

@huacnlee

@huacnlee huacnlee commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Closes #5049.

When the system is in light mode but Zed uses a dark theme, the macOS window chrome — the window border and the titlebar — is rendered light, leaving a pale, low-contrast edge that makes the window look washed out. Zed's own content renders correctly; the affected chrome is drawn by AppKit, not Zed.

Why it happens: NSWindow conforms to NSAppearanceCustomization, so a window's appearance is inherited app → window → view and resolved through effectiveAppearance unless an explicit appearance is set. Zed never set one, so every window inherited the system appearance and AppKit drew the chrome for light mode regardless of the dark theme.

The fix uses the override AppKit provides for exactly this: set NSApplication.appearance app-wide, so every window inherits it. VSCode (window.systemColorTheme) and JetBrains IDEs do the same.

after-light

GPUI

  • Add App::set_window_appearance(Option<WindowAppearance>), the setter paired with the existing App::window_appearance() getter. Some(_) forces a light/dark appearance; None clears the override and follows the system again.
  • macOS sets NSApplication.appearance (Nonenil); no-op on other platforms.
  • Demo: cargo run -p gpui --example window_appearance.

Zed

  • An explicit light/dark theme (or a static theme) forces the matching chrome; System follows the OS.
  • Wired once at startup via a global settings observer, so every window — including the settings window — stays in sync.
Before After
before-light SCR-20260609-nzjk

References: NSAppearanceCustomization · NSApplication.appearance

Release Notes:

  • Fixed the macOS window border and titlebar not matching the selected theme when the system appearance differed (for example, a dark theme under a light system theme). (#5049)

…ance

Add `App::set_window_appearance(Option<WindowAppearance>)`, the setter paired with
the existing `App::window_appearance()` getter. `Some(_)` forces a light/dark
appearance; `None` clears the override and follows the system again.

On macOS this sets `NSApplication.appearance`, which controls the native window
chrome (the window border and titlebar) of every window, so a dark-themed app no
longer shows a light, washed-out window border when the system is in light mode.
Setting it on the application (rather than per window) matches AppKit's app-wide
appearance model, so every window inherits it. No-op on other platforms.

Demo: `cargo run -p gpui --example window_appearance`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 9, 2026
@zed-community-bot zed-community-bot Bot added the community champion Issues filed by our amazing community champions! 🫶 label Jun 9, 2026
Drive App::set_window_appearance from the theme at startup so the native window
chrome (the window border and titlebar) matches the selected theme. An explicit
light/dark theme (or a static theme) forces the matching appearance; System
follows the OS. Wired via a global settings observer, so every window — including
the settings window — stays in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@huacnlee
huacnlee force-pushed the add-set-appearance branch from ae8e5ac to 5f56842 Compare June 9, 2026 08:00
@ChristopherBiscardi ChristopherBiscardi added the platform:macOS Platform-specific feedback for macOS behaviors, features, design, etc label Jun 10, 2026
@huacnlee

huacnlee commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Furthermore, this is also very important. For example, my current gpui-component implementation of a native menu for macOS and Windows is still using the light menu in dark mode because the window doesn't change the NSWindow's appearance information.

SCR-20260610-qjov

For example in Ghostty is correct:

SCR-20260610-qjqv

@Veykril Veykril left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the example pulls its weight here, but otherwise lgtm!

@Veykril Veykril self-assigned this Jul 15, 2026
@huacnlee

Copy link
Copy Markdown
Contributor Author

Is there any things need me to change? or I just waiting to merge?

@Veykril

Veykril commented Jul 28, 2026

Copy link
Copy Markdown
Member

Lets remove the example, otherwise this is good to go I think

@huacnlee

Copy link
Copy Markdown
Contributor Author

done

@Veykril Veykril left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@Veykril
Veykril enabled auto-merge July 28, 2026 08:23
@Veykril
Veykril added this pull request to the merge queue Jul 28, 2026
Merged via the queue into zed-industries:main with commit 8e4e5a3 Jul 28, 2026
36 checks passed
@huacnlee
huacnlee deleted the add-set-appearance branch July 28, 2026 09:08
pull Bot pushed a commit to gaetschwartz/zed that referenced this pull request Jul 29, 2026
…-industries#61789)

### Objective

PR zed-industries#58902 made native macOS window chrome follow the selected theme, but
AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while
the settings observer holds an `App` borrow. `handle.update` then fails
with `RefCell already borrowed`, leaving `Window::appearance()` and its
observers stale.

### Steps to reproduce

1. Set Zed's theme mode to Dark.
2. Set macOS Appearance to Light.
3. Change Zed's theme mode to System.
4. Notice that the window chrome switches to light, but Zed's UI remains
dark.


https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba

### Expectation

Zed should switch to its configured light theme when returning to System
mode.

### Solution

Defer `Window::appearance_changed` to the foreground executor so the
current `App` borrow finishes before refreshing the cached appearance
and notifying observers.

### Testing

- Added a regression test.

### Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A
AtlantisPleb pushed a commit to OpenAgentsInc/omega that referenced this pull request Jul 30, 2026
…-industries#61789)

### Objective

PR zed-industries#58902 made native macOS window chrome follow the selected theme, but
AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while
the settings observer holds an `App` borrow. `handle.update` then fails
with `RefCell already borrowed`, leaving `Window::appearance()` and its
observers stale.

### Steps to reproduce

1. Set Zed's theme mode to Dark.
2. Set macOS Appearance to Light.
3. Change Zed's theme mode to System.
4. Notice that the window chrome switches to light, but Zed's UI remains
dark.

https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba

### Expectation

Zed should switch to its configured light theme when returning to System
mode.

### Solution

Defer `Window::appearance_changed` to the foreground executor so the
current `App` borrow finishes before refreshing the cached appearance
and notifying observers.

### Testing

- Added a regression test.

### Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

(cherry picked from commit a11083f)
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…58902)

Closes zed-industries#5049.

When the system is in light mode but Zed uses a dark theme, the macOS
window chrome — the window border and the titlebar — is rendered light,
leaving a pale, low-contrast edge that makes the window look washed out.
Zed's own content renders correctly; the affected chrome is drawn by
AppKit, not Zed.

Why it happens: `NSWindow` conforms to `NSAppearanceCustomization`, so a
window's appearance is inherited app → window → view and resolved
through `effectiveAppearance` unless an explicit appearance is set. Zed
never set one, so every window inherited the *system* appearance and
AppKit drew the chrome for light mode regardless of the dark theme.

The fix uses the override AppKit provides for exactly this: set
`NSApplication.appearance` app-wide, so every window inherits it. VSCode
(`window.systemColorTheme`) and JetBrains IDEs do the same.

<img width="2600" height="1346" alt="after-light"
src="https://github.com/user-attachments/assets/c31376af-8f51-4c36-a62f-55714f63b59c"
/>

## GPUI

- Add `App::set_window_appearance(Option<WindowAppearance>)`, the setter
paired with the existing `App::window_appearance()` getter. `Some(_)`
forces a light/dark appearance; `None` clears the override and follows
the system again.
- macOS sets `NSApplication.appearance` (`None` → `nil`); no-op on other
platforms.
- Demo: `cargo run -p gpui --example window_appearance`.

## Zed

- An explicit light/dark theme (or a static theme) forces the matching
chrome; `System` follows the OS.
- Wired once at startup via a global settings observer, so every window
— including the settings window — stays in sync.

| Before | After |
| --- | --- |
| <img width="3630" height="1878" alt="before-light"
src="https://github.com/user-attachments/assets/44c9ab66-c0cf-433a-93b0-17446b34a284"
/> | <img width="3686" height="1832" alt="SCR-20260609-nzjk"
src="https://github.com/user-attachments/assets/32b0412b-0913-4e68-acba-bf39dcb6aa6a"
/> |


References:
[`NSAppearanceCustomization`](https://developer.apple.com/documentation/appkit/nsappearancecustomization)
·
[`NSApplication.appearance`](https://developer.apple.com/documentation/appkit/nsapplication/appearance)

Release Notes:

- Fixed the macOS window border and titlebar not matching the selected
theme when the system appearance differed (for example, a dark theme
under a light system theme).
([zed-industries#5049](zed-industries#5049))

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
…-industries#61789)

### Objective

PR zed-industries#58902 made native macOS window chrome follow the selected theme, but
AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while
the settings observer holds an `App` borrow. `handle.update` then fails
with `RefCell already borrowed`, leaving `Window::appearance()` and its
observers stale.

### Steps to reproduce

1. Set Zed's theme mode to Dark.
2. Set macOS Appearance to Light.
3. Change Zed's theme mode to System.
4. Notice that the window chrome switches to light, but Zed's UI remains
dark.


https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba

### Expectation

Zed should switch to its configured light theme when returning to System
mode.

### Solution

Defer `Window::appearance_changed` to the foreground executor so the
current `App` borrow finishes before refreshing the cached appearance
and notifying observers.

### Testing

- Added a regression test.

### Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A
HyacinthHaru pushed a commit to HyacinthHaru/z3rm that referenced this pull request Aug 16, 2026
…-industries#61789)

PR zed-industries#58902 made native macOS window chrome follow the selected theme, but
AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while
the settings observer holds an `App` borrow. `handle.update` then fails
with `RefCell already borrowed`, leaving `Window::appearance()` and its
observers stale.

1. Set Zed's theme mode to Dark.
2. Set macOS Appearance to Light.
3. Change Zed's theme mode to System.
4. Notice that the window chrome switches to light, but Zed's UI remains
dark.

https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba

Zed should switch to its configured light theme when returning to System
mode.

Defer `Window::appearance_changed` to the foreground executor so the
current `App` borrow finishes before refreshing the cached appearance
and notifying observers.

- Added a regression test.

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A

(cherry picked from commit a11083f)
playdohface pushed a commit to playdohface/zed that referenced this pull request Aug 29, 2026
…58902)

Closes zed-industries#5049.

When the system is in light mode but Zed uses a dark theme, the macOS
window chrome — the window border and the titlebar — is rendered light,
leaving a pale, low-contrast edge that makes the window look washed out.
Zed's own content renders correctly; the affected chrome is drawn by
AppKit, not Zed.

Why it happens: `NSWindow` conforms to `NSAppearanceCustomization`, so a
window's appearance is inherited app → window → view and resolved
through `effectiveAppearance` unless an explicit appearance is set. Zed
never set one, so every window inherited the *system* appearance and
AppKit drew the chrome for light mode regardless of the dark theme.

The fix uses the override AppKit provides for exactly this: set
`NSApplication.appearance` app-wide, so every window inherits it. VSCode
(`window.systemColorTheme`) and JetBrains IDEs do the same.

<img width="2600" height="1346" alt="after-light"
src="https://github.com/user-attachments/assets/c31376af-8f51-4c36-a62f-55714f63b59c"
/>

## GPUI

- Add `App::set_window_appearance(Option<WindowAppearance>)`, the setter
paired with the existing `App::window_appearance()` getter. `Some(_)`
forces a light/dark appearance; `None` clears the override and follows
the system again.
- macOS sets `NSApplication.appearance` (`None` → `nil`); no-op on other
platforms.
- Demo: `cargo run -p gpui --example window_appearance`.

## Zed

- An explicit light/dark theme (or a static theme) forces the matching
chrome; `System` follows the OS.
- Wired once at startup via a global settings observer, so every window
— including the settings window — stays in sync.

| Before | After |
| --- | --- |
| <img width="3630" height="1878" alt="before-light"
src="https://github.com/user-attachments/assets/44c9ab66-c0cf-433a-93b0-17446b34a284"
/> | <img width="3686" height="1832" alt="SCR-20260609-nzjk"
src="https://github.com/user-attachments/assets/32b0412b-0913-4e68-acba-bf39dcb6aa6a"
/> |


References:
[`NSAppearanceCustomization`](https://developer.apple.com/documentation/appkit/nsappearancecustomization)
·
[`NSApplication.appearance`](https://developer.apple.com/documentation/appkit/nsapplication/appearance)

Release Notes:

- Fixed the macOS window border and titlebar not matching the selected
theme when the system appearance differed (for example, a dark theme
under a light system theme).
([zed-industries#5049](zed-industries#5049))

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
playdohface pushed a commit to playdohface/zed that referenced this pull request Aug 29, 2026
…-industries#61789)

### Objective

PR zed-industries#58902 made native macOS window chrome follow the selected theme, but
AppKit can synchronously invoke `viewDidChangeEffectiveAppearance` while
the settings observer holds an `App` borrow. `handle.update` then fails
with `RefCell already borrowed`, leaving `Window::appearance()` and its
observers stale.

### Steps to reproduce

1. Set Zed's theme mode to Dark.
2. Set macOS Appearance to Light.
3. Change Zed's theme mode to System.
4. Notice that the window chrome switches to light, but Zed's UI remains
dark.


https://github.com/user-attachments/assets/f91962cc-76ba-4d8e-b587-2a2343f365ba

### Expectation

Zed should switch to its configured light theme when returning to System
mode.

### Solution

Defer `Window::appearance_changed` to the foreground executor so the
current `App` borrow finishes before refreshing the cached appearance
and notifying observers.

### Testing

- Added a regression test.

### Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- N/A
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement community champion Issues filed by our amazing community champions! 🫶 platform:macOS Platform-specific feedback for macOS behaviors, features, design, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bad window look on macOS with light theme enabled when using dark theme in Zed

3 participants