GNOME themes, an incomplete status report, and how you can help

- Tags: gnome

"Themes in GNOME" is a complicated topic in technical and social terms. Technically there are a lot of incomplete moving parts; socially there is a lot of missing documentation to be written, a lot of miscommunication and mismatched expectations.

The following is a brief and incomplete, but hopefully encouraging, summary of the status of themes in GNOME. I want to give you an overall picture of the status of things, and more importantly, an idea of how you can help. This is not a problem that can be solved by a small team of platform developers.

I wish to thank Alexander Mikhaylenko for providing most of the knowledge in this post.

Frame of reference

First, I urge you to read Cassidy James Blaede's comprehensive "The Need for a FreeDesktop Dark Style Preference". That gives an excellent, well-researched introduction to the "dark style" problem, the status quo on other platforms, and exploratory plans for GNOME and Elementary from 2019.

Go ahead, read it. It's very good.

There is also a GUADEC talk about Cassidy's research if you prefer to watch a video.

Two key take-aways from this: First, about this being a preference, not a system-enforced setting:

I’m explicitly using the language “Dark Style Preference” for a reason! As you’ll read further on, it’s important that this is treated as a user “preference,” not an explicit “mode” or strictly-enforced “setting.” It’s also not a “theme” in the sense that it just swaps out some assets, but is a way for the OS to support a user expressing a preference, and apps to respond to that preference.

Second, about the accessibility implications:

Clearly there’s an accessibility and usability angle here. And as with other accessibility efforts, it’s important to not relegate a dark style preference to a buried “Universal Access” or “Accessibility” feature, as that makes it less discoverable, less tested, and less likely to be used by folks who could greatly benefit, but don’t consider themselves “disabled.”

Libadwaita and the rest of the ecosystem

Read the libadwaita roadmap; it is very short, but links to very interesting issues on gitlab.

For example, this merge request is for an API to query the dark style and high-contrast preferences. It has links to pending work in other parts of the platform: libhandy, gsettings schemas, portals so that containerized applications can query those preferences.

As far as I understand it, applications that just use GTK3 or libhandy can opt in to supporting the dark style preference — it is opt-in because doing that unconditionally in GTK/libhandy right now would break existing applications.. If your app uses libadwaita, it is assumed that you have opted into supporting that preference, since libadwaita's widgets already make that assumption, and it is not API-stable yet — so it can make that assumption from the beginning.

There is discussion of the accessibility implications in the design mockups.

CSS parity across implementations

In GNOME we have three implementations of CSS:

  • librsvg uses servo's engine for CSS selector matching, and micro-parsers for CSS values based on servo's cssparser.

  • GTK has its own CSS parser and processor.

  • Gnome-shell uses an embedded version of libcroco for parsing, but it does most of the selector matching and cascading with gnome-shell's own Shell Toolkit code.

None of those implementations supports @media queries nor custom properties with var(). That is, unlike in the web platform, GNOME applications cannot have this in their CSS:

@media (prefers-color-scheme: dark) {
  /* styles for dark style */
}

@media (prefers-color-scheme: light) {
  /* styles for light style */
}

Or even declaring colors in a civilized fashion:

:root {
  --main-bg-color: pink;
}

some_widget {
  background-color: var(--main-bg-color);
}

Or combining the two:

@media (prefers-color-scheme: dark) {
  :root {
    --main-bg-color: /* some nice dark background color */;
    --main-fg-color: /* a contrasty light foreground */;
  }
}

@media (prefers-color-scheme: light) {
  :root {
    --main-bg-color: /* some nice light background color */;
    --main-fg-color: /* a contrasty dark foreground */;
  }
}

some_widget {
  background-color: var(--main-bg-color);
}

Boom. I think this would remove some workarounds we have right now:

  • Just like GTK, libadwaita generates four variants of the system's stylesheet using scss (regular, dark, high-contrast, high-contrast-dark). This would be obviated with @media queries for prefers-color-scheme, prefers-contrast, inverted-colors as in the web platform.

  • GTK has a custom @define-color keyword, but neither gnome-shell nor librsvg support that. This would be obviated with CSS custom properties - the var() mechanism. (I don't know if some "environmental" stuff would be better done as env(), but none of the three implementations support that, either.)

Accent colors

They are currently implemented with GTK's @define-color, which is not ideal if the colors have to trickle down from GTK to SVG icons, since librsvg doesn't do @define-color - it would rather have var() instead.

Of course, gnome-shell's libcroco doesn't do @define-color either.

Look for @accent_color, @accent_bg_color, @warning_color, etc. in the default stylesheet, or better yet, write documentation!

The default style:

Default blue style

Accent color set to orange (e.g. tweak it in GTK's CSS inspector):

Orange accents for widgets

/* Standalone, e.g. the "Page 1" label */
@define-color accent_color @orange_5;

/* background+text pair */
@define-color accent_bg_color @orange_4;
@define-color accent_fg_color white;

Custom widgets

Again, your app's custom stylesheet for its custom widgets can use the colors defined through @define-color from the system's stylesheet.

Recoloring styles

You will be able to do this after it gets merged into the main branch, e.g. recolor everything to sepia:

Adwaita recolored to sepia

@define-color headerbar_bg_color #eedcbf;
@define-color headerbar_fg_color #483a22;

@define-color bg_color #f9f3e9;
@define-color fg_color #483a22;

@define-color dark_fill_color shade(#f9f3e9, .95);

@define-color accent_bg_color @orange_4;
@define-color accent_color @orange_5;

Of course shade() is not web-platform CSS, either. We could keep it, or redo it by implementing calc() function for color values.

Recoloring icons

Currently GTK takes some defined colors and creates a chunk of CSS to inject into SVG for icons. This has some problems.

There is also some discussion about standardizing recolorable icons across desktop environments.

How you can help

Implement support for @media queries in our three CSS implementations (librsvg, gnome-shell, GTK). Decide how CSS media features like prefers-color-scheme, prefers-contrast, inverted-colors should interact with the GNOME's themes and accessibility, and decide if we should use them for familiarity with the web platform, or if we need media features with different names.

Implement support for CSS custom properties - var() in our three CSS implementations. Decide if we should replace the current @define-color with that (note that @define-color is only in GTK, but not in librsvg or gnome-shell).

See the libadwaita roadmap and help out!

Port applications to use the proposed APIs for querying the dark style preference. There are a bunch of hacky ways of doing it right now; they need to be migrated to the new system.

Personally I would love help with finishing to port gnome-shell's styles to Rust - this is part of unifying librsvg's and gnome-shell's CSS machinery.