Light and dark schemes
Every theme ships a light and a dark color scheme. The visitor picks Auto (follows the operating system, the
default), Light or Dark from the switcher in the header; the choice is kept in the browser (localStorage).
Installing a theme installs both schemes: there is nothing to configure.
How it works
Section titled “How it works”The switcher is shared by every theme (src/Themes/color_scheme/, installed by
SyliusStarter\Themes\ColorScheme):
- a script in
<head>(sylius_shop.base#stylesheetshook, before the stylesheets, so the page is painted in the right scheme) sets<html data-bs-theme="light|dark">and<html data-color-scheme-mode="auto|light|dark">, and follows the operating system while the mode is Auto; - the switcher itself, a Bootstrap dropdown hooked in
sylius_shop.base.header.content(themes_color_scheme, priority 150, between the account links and the cart). It usesbtn btn-icon btn-transparentlike the other header icons, so it takes the theme’s header styles; _color-scheme.scss, the few switcher styles, which only rely on Bootstrap’s runtime variables. In the dark scheme, it also points the Sylius components compiled with constant dark colors (.nav,.navbar,.pagination,.btn-transparent,.btn-outline-gray,.btn-outline-dark) to Bootstrap’s runtime variables, with the weight of a class and before the theme styles: a theme rule always wins.
data-bs-theme is Bootstrap’s own color mode attribute: on top of the theme styles, native Bootstrap components
(form controls, close buttons…) switch to their dark variant.
Theme stylesheet structure
Section titled “Theme stylesheet structure”A theme compiles all its styles twice, once per scheme, scoped on the <html> attribute:
@import "fonts"; // @font-face rules, outside the scopes@import "color-scheme"; // shared switcher styles
// Light is also the scheme without JavaScript.:where(:root:not([data-bs-theme="dark"])) { @import "palette-light"; @import "theme";}
:where(:root[data-bs-theme="dark"]) { @import "palette-dark"; @import "theme";}The scopes are wrapped in :where(), which adds no specificity: every theme rule keeps the weight it would have
without schemes, so a theme a rule still loses to Bootstrap’s .btn, for example.
_palette-light.scssand_palette-dark.scssdeclare$color-mode(lightordark) and every color of the scheme, under the same variable names._variables.scssholds what does not depend on the scheme (fonts, radii, mixins) and the variables derived from the palette. It defines$dark: $color-mode == dark;._theme.scssimportsvariablesthen every partial.
In the partials:
:root { ... }andhtml { ... }are written@include scheme-root { ... }(from_color-scheme.scss): it targets the<html>element of that scheme with the weight of:root, so that the theme’s runtime variables still win over Bootstrap’s own:rootand[data-bs-theme]blocks (:root :rootwould match nothing);- a value that differs between schemes uses the palette, or
if($dark, <dark value>, <light value>); a rule that only exists in one scheme goes in@if $dark { ... }; - Bootstrap’s
--bs-*-rgbvariables are computed withchannels($color)(defined in_variables.scss) rather than hard-coded; - colors are never hard-coded in a partial when they belong to the palette, and never in a Twig template: an
illustration that changes with the scheme is rendered twice, with a
--light/--darkmodifier class, and the styles hide the other one (display: none), with SVG ids prefixed by the scheme; - a
@keyframesrule is emitted twice under the same name: if it uses a color that differs between schemes, give it a scheme suffix (#{$color-mode}) or animate a CSS custom property instead.
Every scheme must keep the accessibility rules of the theme: 4.5:1 for text, 3:1 for form field outlines and focus indicators (see Accessibility).