Skip to content

feat(theme): add Glass design language - #3130

Open
afonsojramos wants to merge 32 commits into
mainfrom
native-theme-support
Open

feat(theme): add Glass design language#3130
afonsojramos wants to merge 32 commits into
mainfrom
native-theme-support

Conversation

@afonsojramos

@afonsojramos afonsojramos commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

After simmering for 2 days @setchy 👀

Adds a second design language, Glass, next to the existing look (now Classic), selectable under Settings → Appearance. Under Glass the whole window becomes one continuous translucent material: the sidebar dissolves into icons floating on the glass, controls (buttons, selects, checkboxes, radios, count pills) render as stepped translucent tiles, notification and repo rows float as rounded cards with glass hover states, and tooltips/popovers come out frosted. Classic is pixel-unchanged: everything Glass lives behind [data-theme='glass'] plus a .gitify-translucent gate, so there is no risk to the default experience.

image image image

Design decisions

  • Native material where the OS offers one. On macOS the window gets a real popover vibrancy material, wired over IPC and ordered so switching designs at runtime never flashes black; the material also follows the app's light/dark mode via nativeTheme sync. Other platforms fall back to CSS backdrop-filter.
  • One token layer, zero component branching. Chrome values (radius, tints, stepped control fills, hairlines) are CSS custom properties re-pointed per language, and Primer's own design tokens are overridden at the data-color-mode boundary, so components stay stock Primer and the entire language lives in App.css.
  • Accessibility degradation. OS Reduce Transparency / Increase Contrast switches Glass to solid, legible surfaces, and the vibrancy material is withheld entirely.
  • Native-feeling details. Desaturated status palette, borderless frosted pills, translucent label tokens that keep their GitHub hue, slim overlay scrollbar, and hover actions that fade row text into the glass instead of painting an opaque band.

Validation

  • Full test suite: 160 files / 1245 tests passing; tsc, lint and format clean.
  • Exercised live on macOS in light and dark: notifications list, settings, filters; hover, focus, disabled and reduced-transparency states; runtime Classic ↔ Glass switching.

New concepts

Theme tokens that re-resolve per language (Tailwind @theme inline)

The radius/shadow/tint utilities are backed by plain CSS variables (--gitify-*) that each design language re-points under its own [data-theme] scope. Declaring them with @theme inline (instead of @theme) makes Tailwind re-resolve the utilities against the active scope rather than freezing them at :root.

@theme inline {
  --radius-gitify-md: var(--gitify-radius-md); /* follows the active [data-theme] */
}

This is why one component tree can serve two languages with no JS branching: the swap is a single attribute on the root. Reach for it when a whole token layer swaps at once; for one-off per-component tweaks, a direct class is simpler.


Note: I now fully prefer this theme 👀

Drop `transparent: true` (it makes behind-window vibrancy render near-opaque over
the desktop) and switch the material to `popover`; keep `visualEffectState: active`
so the menubar popup stays translucent while unfocused. Sync `nativeTheme.themeSource`
with the color mode via a new SET_NATIVE_THEME IPC so dark Glass gets a dark material
instead of light text on a light frost. Clear the Primer `[data-color-mode]` wrapper's
opaque background so the material shows through the whole window.

Also removes the Increase contrast setting, which forced Glass to a solid surface and
whose useAppearance/theme wiring is intertwined with the changes above.
Under translucency: row action buttons become bare icons (no hover band or button
fill) with the row text fading out under them on hover so it never collides;
the scrollbar track goes transparent; and the glass tints are lowered a step so
more of the material shows through.
…witch

applyWindowVibrancy only cleared the window background when disabling, so
switching Classic (which paints an opaque backdrop) to Glass left that backdrop
in place and blocked the vibrancy material from sampling the desktop. Clear it on
enable too; `#00000000` works without a transparent window because the vibrancy
view provides the translucency. Adds regression tests for both toggle sides.
Handle the setNativeTheme IPC rejection (log via rendererLogError) instead of
leaving an unhandled promise, matching the setWindowVibrancy call, and note that
the sync is intentionally not macOS-gated. Adds coverage for the Glass-clamped
path (Glass + DARK_DIMMED still syncs 'dark').
@afonsojramos
afonsojramos requested a review from setchy as a code owner July 31, 2026 22:55
@github-actions github-actions Bot added the enhancement New feature or enhancement to existing functionality label Jul 31, 2026

@setchy setchy 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.

Marking while I have time to review.

Comment thread src/main/handlers/system.ts
Comment thread src/renderer/__mocks__/state-mocks.ts
Comment thread src/renderer/components/settings/AppearanceSettings.tsx Outdated
@setchy

setchy commented Aug 1, 2026

Copy link
Copy Markdown
Member

Super sharp enhancement, @afonsojramos. I love that we're able to keep the design components as Primer and apply new theme/style overrides. Nice work.

Left a few inline comments for my own curiosity

Comment thread src/renderer/hooks/useAppearance.ts Outdated
…and the OS

Restore the Increase contrast setting (shown for Classic only) and additionally honour
the OS "Increase contrast" accessibility preference via `prefers-contrast: more`. Either
one selects Primer's `*_high_contrast` schemes for Classic; Glass takes its solid
fallback instead, and that same preference now also withholds Glass's vibrancy.
The inert zoom/interval/volume/shortcut value displays are self-explanatory from
`pointer-events-none` and `tabIndex={-1}`, so the repeated comment was just noise.
@afonsojramos
afonsojramos requested a review from setchy August 2, 2026 16:30
@setchy

setchy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Was doing more manual testing this morning - the notification type icon colors appear broken [always black]

@setchy

setchy commented Aug 4, 2026

Copy link
Copy Markdown
Member

When in glass mode, the checkbox for Show full notification title is missing some styles
2026-08-04T15-09-06 565Z-Gitify-screenshot

you can still toggle it, but when unchecked its missing the checkbox outline

@afonsojramos

Copy link
Copy Markdown
Member Author

Are you sure that you ran everything correctly? This is what it should look like
image

@setchy

setchy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Are you sure that you ran everything correctly?

Yup, fairly certain - try toggling the theme - looks like several other components like the footer buttons also loose their styling when set to Glass + System

Screen.Recording.2026-08-04.at.5.05.37.PM.mov

@setchy

setchy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Was doing more manual testing this morning - the notification type icon colors appear broken [always black]

branch screenshot
main Screenshot 2026-08-04 at 5 08 17 PM
native-theme-support - Classic Screenshot 2026-08-04 at 5 10 27 PM
native-theme-support - Glass Screenshot 2026-08-04 at 5 09 30 PM

The Glass tints, borders and control fills were keyed off
`[data-color-mode='light'|'dark']`, but the System theme renders
`data-color-mode="auto"`, so neither matched and every token resolved to nothing:
form controls lost their fill and tooltips fell back to Primer's solid styling.
Define them once with `light-dark()`, which resolves against the wrapper's
`color-scheme` and therefore follows the OS under `auto`.
Primer declares its `--fgColor-*` tokens on the `[data-color-mode]` wrapper, not the
root, so the `--gitify-icon-*` / `--gitify-link` / `--gitify-footer` indirections built
on them were invalid at `:root` and every status icon fell back to inherited black —
in Classic as well as Glass. Declare them on the wrapper instead, keeping the
desaturated palette scoped to Glass.
The background, scrollbar and counter tokens were declared under
`[data-color-mode="light"|"dark"]`, but the System theme renders
`data-color-mode="auto"`, so neither matched and all of them resolved to nothing.
Key them off the attribute's presence and pick each pair with `light-dark()`.
Restoring the icon tokens for Classic also activated the desaturated colour-mix
palette under Glass, which had never actually applied (the tokens were invalid
before the scoping fix), so Glass icons had always rendered monochrome. Make that
explicit: under Glass the type icons follow the surrounding text via
`currentColor`, and the accessibility degradation block still restores the full
colour coding.
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

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

Labels

enhancement New feature or enhancement to existing functionality

Development

Successfully merging this pull request may close these issues.

2 participants