Update thema's en plugins ter ondersteuning van automatische donkere modus

Previously, all colors in Discourse were stored as SCSS variables. To support automatic dark mode color scheme switching, we have converted these colors in core to custom CSS properties. You can easily see the full list in the inspector now:

Themes and plugins need to switch all the $color SCSS variables used in stylesheets to the --color CSS property equivalents. In most cases, this is a simple find and replace task:

-   background-color: $primary-very-low;
+   background-color: var(--primary-very-low);

But there are some cases where a theme or a plugin is using a more complex variation of a color, for example, when darkening or lightening using SCSS color functions. These cases require a more complex refactoring, and for this we have added the capacity to extend color definitions in themes and plugins.

In plugins

This commit in the discourse-encrypt plugin is a good and simple example of such a refactor. It moves a mix($color1, $color2) SCSS declaration into a separate file and stores it as a CSS custom property. Then the new file is registered as a :color_definitions asset which ensures that the newly declared color property is included in the color definitions stylesheet.

In themes

In themes, you can do the same thing by declaring CSS custom properties in the common/color_definitions.scss stylesheet. You can look at this commit in the graceful theme for an example.

Some additional notes

  • when using transparent colors via the rgba($color, 0.5) function, SCSS accepts HEX and RGB colors in the first parameter, whereas CSS custom properties only accept an RGB color. That is why we have introduced the hexToRGB() helper and some properties with the --rgb suffix in the color definitions. An example:
// color_definitions.scss
:root {
  --primary: #{$primary};
  --primary-rgb: #{hexToRGB($primary)};
}

// other stylesheet
.element {
  background-color: rgba(var(--primary-rgb), 0.05);
}
  • note that in the snippet above, the SCSS variable is interpolated when passed to a custom property. That is a requirement in SCSS, see Sass: Property Declarations for more details.
  • the CSS var() declaration can fallback to a second value if the first one is not available, as in, when writing var(--color1, red), CSS will fallback to the red color if the --color1 property is not found. In plugins, we use the SCSS color variables as fallbacks to ensure compatibility with previous versions of Discourse. So the earlier example, would look like this with a fallback:
-   background-color: $primary-very-low;
+   background-color: var(--primary-very-low, $primary-very-low);

This document is version controlled - suggest changes on github.

24 likes

I’m not very good at this stuff and it’ll take me a while to figure this out myself. . . Does this mean that all themes that referred to colors before are now going to be broken?

6 likes

No, not at all. SCSS variables in themes will continue to work for a long time.

But any colors outputted via SCSS variables will stay static, i.e. they cannot be dynamically switched to a new color scheme when a browser goes from normal to dark mode. So those themes/plugins will continue to work, they just won’t be compatible with automatic dark mode switching.

13 likes

Thanks for the instructions. Is there a way to also change a background image depending on dark/light mode? (I’ve used the theme switcher component to do that.) Would a CSS class indicating the mode be possible?

2 likes

Great question, I tried this and noticed that we didn’t properly support using background images or theme variables in the special color definitions stylesheet. So I made some fixes in core, and you should be able to do this now (make sure you pull the latest core).

So, if you have two images in your theme or theme component, with SCSS vars of $bg-light and $bg-dark respectively, you can add this to your color_definitions.scss stylesheet:


$bg: url(dark-light-choose($bg-light, $bg-dark));

:root {
  --custom-bg: #{$bg};
}

And then you can use var(--custom-bg) in your regular stylesheet.

8 likes

For an image, all you need to do is the vanilla prefers-dark-theme CSS media query.

That won’t work well in all cases, because the media query is not aware of the user’s preferences. Users can disable auto-dark-mode switching, but the media query won’t be aware of that, and it will result in the background meant for the dark color scheme being rendered.

4 likes

Is het mogelijk voor Discourse om ook een CSS-klasse toe te voegen aan de <body> voor het kleurenschema of de kleurenschema-ID? Het lijkt erop dat dit veel gemakkelijker zou zijn.

Ik probeer een probleem op te lossen tijdens een thema-conversie waarbij ik veel verschillende CSS-regels en variabelen nodig heb, en het wordt een complexe puinhoop in het color_definitions.scss-bestand.

Als ik zoiets zou kunnen doen in een geĂŻsoleerd SCSS-bestand in het thema, zou het 5 minuten duren om iets te doen waar ik nu lang over doe om het uit te zoeken met color_definitions.scss:

body.dark-palette .some-thing {
  // some styles
}

body.light-palette .some-thing {
  // some styles
}

Het is mogelijk dat ik niet begrijp wat de beste manier is om dit te doen, maar het lijkt erop dat veel dingen voor elke variabele moeten worden gedaan — het instellen van een van de CSS-variabelen kost 17 regels in de editor, ver weg van de ene kleine plek waar het zal worden gebruikt:

  • definieer een lichtmoduswaarde in een SCSS-variabele (dit kunnen 6 regels in de editor zijn voor iets als een lineaire gradiĂ«nt)
  • definieer een donkere-moduswaarde in een SCSS-variabele (nog eens 6 regels)
  • definieer een SCSS-variabele met dark-light-choose (4 regels vanwege lange variabelenamen)
  • wijs die laatste SCSS-variabele toe aan een CSS-variabele in :root (1 regel)

Het kost ook tijd om uit te zoeken hoe je al die vier dingen moet benoemen, omdat het globaal is.

Ja, je kunt eindigen met veel regels code in de bestanden met kleurendefinities, vooral voor verlopen.

Het is nog steeds de meest geschikte “plek” ervoor in mijn optiek. Het staat weliswaar weg van het element waar het wordt gebruikt, maar het is een handige, enkele plek die kleuren/verlopen beheert die wisselen op basis van lichte/donkere modus.

Een alternatief is om iets als dit te gebruiken:

@container style(--scheme-type: light){
  body{
    background: red;
  }
}

Onze kleurenschema’s worden geleverd met een --scheme-type eigenschap die light is voor lichte schema’s en dark voor donkere schema’s. Recente browsers ondersteunen container queries, dus dit zou moeten doen wat je hier probeert te bereiken zonder een klasse toe te voegen aan de body van de pagina.

My CSS/SCSS skills aren’t great. Maybe this is easier for people who are deep into those things.

It was making a mess of the color_definitions.scss file, so I moved it into another file in scss/ so I could import it. I’m not sure how to name it all, so it’s looking like this.

I think it would be easier to do with a body class (or maybe some more examples), but in any case, it’s working for now. I don’t know how container queries work, but I’ll research it later.

Note that style queries for custom properties don’t work on Firefox as of today (2025/11/14).