riv riv - 7 months ago 87
Less Question

Themed variables in LESS/SASS

I'm looking to support multiple themes in my app - moreover, I want to be able to dynamically change themes, either by changing a class on the

body
element, or even have different parts of the app use different themes.

In my previous project, I did it by adding explicit rules every time I need to use a theme-specific variable:

.theme-light & { background-color: @theme-light-background; }
.theme-dark & { background-color: @theme-dark-background; }


However, that approach does not scale well and adds unnecessary bloat to source files.

Now, I'm looking for a more automated approach for this. I.e. the following

.button {
border-radius: 4px;
background-color: @ui-background;
color: @ui-foreground;
border: 1px solid mix(@ui-background, @ui-foreground, 50%);
}


would turn into something like

.button {
border-radius: 4px;
border: 1px solid #808080;
/* normally we wouldn't expect this to appear here, but in our case
both themes have the same border color so we can't tell the difference */
}
.theme-light .button {
background-color: #fff;
color: #000;
}
.theme-dark .button {
background-color: #000;
color: #fff;
}


As far as I can tell, neither LESS nor SASS can do this in a natural way. It seems that it wouldn't be too difficult to implement it as a separate post-processor, that builds stylesheets for every theme, then compares them and scopes the differences into the corresponding "namespaces". I suspect that something like this might already exist, but I can't find anything.

Any suggestions?

Answer Source

Not sure about Less, but in Sass it can be implemented relatively easy by storing theme information into maps and using ability to pass content blocks into mixins using @content. Here is example of how it may look like, quite fast solution but you can get an idea:

// Themes definition
//  - First level keys are theme names (also used to construct theme class names)
//  - Second level keys are theme settings, can be referred as theme(key)
$themes: (
    light: (
        background: #fff,
        foreground: #000,
    ),
    dark: (
        background: #000,
        foreground: #fff,
    ),
);

// Internal variable, just ignore 
$_current-theme: null;

// Function to refer to theme setting by name
// 
// @param string $name  Name of the theme setting to use
// @return mixed
@function theme($name) {
    @if ($_current-theme == null) {
        @error "theme() function should only be used into code that is wrapped by 'theme' mixin";
    }
    @if (not map-has-key(map-get($themes, $_current-theme), $name)) {
        @warn "Unknown theme key '#{$name}' for theme '#{$_current-theme}'";
        @return null;
    }
    @return map-get(map-get($themes, $_current-theme), $name);
}

// Theming application mixin, themable piece of style should be wrapped by call to this mixin 
@mixin theme() {
    @each $theme in map-keys($themes) {
        $_current-theme: $theme !global;
        .theme-#{$theme} & {
            @content;
        }
    }
    $_current-theme: null !global;
}

.button {
    border-radius: 4px;
    @include theme() {
        background-color: theme(background);
        color: theme(foreground);
    }
}

This piece of code will give you this result:

.button {
  border-radius: 4px;
}
.theme-light .button {
  background-color: #fff;
  color: #000;
}
.theme-dark .button {
  background-color: #000;
  color: #fff;
}

Looks pretty close to what you're trying to achieve. You can play with this snippet at Sassmeister.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download