Skip to content

Instantly share code, notes, and snippets.

@loilo
Last active April 9, 2024 20:01
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save loilo/dd5639089d837e95c22a706260b26706 to your computer and use it in GitHub Desktop.
Save loilo/dd5639089d837e95c22a706260b26706 to your computer and use it in GitHub Desktop.
Sass Dark/Light Theme Mixin

Sass Dark/Light Theme Mixin

This is a Sass mixin to handle a 3-way dark mode. It relies on a data-theme attribute on your <html> element with a value of light or dark. If data-theme is absent (i.e. it's neither light nor dark), the system's preferred mode is used.

body {
  // matches data-theme="light" or data-theme="auto" with system instructing light mode
  @include light {
    background: white;
    color: black;
  }
  
  // matches data-theme="dark" or data-theme="auto" with system instructing dark mode
  @include dark {
    background: black;
    color: white;
  }
}

See the example above on sassed

Caveat: This mixin targets modern browsers which support the :where() pseudo selector. There is an older revision of this Gist with more caveats, but also with support for older browsers, which you can find here.

@use 'sass:selector';
@use 'sass:string';
@use 'sass:list';
@mixin mode($mode) {
$opposite-mode: '';
@if $mode == 'light' {
$opposite-mode: 'dark';
} @else {
$opposite-mode: 'light';
}
$individual-selectors: selector.parse(&);
$new-selectors: ();
// Split into individual selectors
@each $individual-selector in $individual-selectors {
// Split by :: to find pseudo elements
$parts: string.split(#{$individual-selector}, '::', 2);
@if list.length($parts) > 1 {
// Selector has pseudo elements:
// Split up the selector, apply mode modifier and stick the pseudo element on again
$new-selectors: list.append(
$new-selectors,
"#{selector.append(list.nth($parts, 1), ':where(:root:not([data-theme=#{$mode}]):not([data-theme=#{$opposite-mode}]) *)')}::#{list.nth($parts, 2)}",
comma
);
} @else {
// Selector has no pseudo elements: Simply append the :where() clause
$new-selectors: list.append(
$new-selectors,
selector.append($individual-selector, ':where(:root:not([data-theme=#{$mode}]):not([data-theme=#{$opposite-mode}]) *)'),
comma
);
}
}
@media (prefers-color-scheme: $mode) {
@at-root #{$new-selectors} {
@content;
}
}
@at-root #{$new-selectors} {
@content;
}
}
@mixin light {
@include mode('light') {
@content;
}
}
@mixin dark {
@include mode('dark') {
@content;
}
}
@wonsuc
Copy link

wonsuc commented Mar 27, 2024

Thank you for your efforts. Works good :)

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