Sass configuration map with default values
Asked Answered
E

3

6

I am creating css using SASS and would like to make it possible for another developer to create a custom css by changing sass variables. This works fine when I in my base file use a single variable like this:

$text-color: #000 !default;

To test the override I create a new project where I first declare an override for the variable and then import the "base" sass file.

$text-color: #0074b;    
@import "base-file";

But I would also like to use maps for configuration but then I do not get the override to work. How should I use configuration maps that can be overriden?

$colors: (text-color: #000, icon-color: #ccc );

Adding !default after #000 gives me a compilation error: expected ")", was "!default,") Adding !default after the ) gives no error but the variables does not get overwritten either.

Any ideas on what I am doing wrong?

Elstan answered 16/11, 2014 at 13:58 Comment(5)
It's not clear here what's actually happening vs what you're expecting to happen. The value of the variable $colors is (text-color: #000, icon-color: #ccc ). There's no overwriting a portion of it using !default, you have to overwrite the entire thing.Hydrolysis
Thank you for clearing that up. So if I create a new variable $colors and overwrite everything it should work? What happens if a create a new variable $colors: (text-color: #fff); What happens to the icon-color in the original map? Will it become null?Elstan
Anything you don't specify as part of the mapping will not exist. Essentially, it will behave as null.Hydrolysis
Great, thanks. Then I understand the behaviour and I should be able to get it working.Elstan
map.deep-merge($map1, $map2) #26958253Heavyweight
P
10

I don't think the functionality you want exists in standard Sass. I built this function though that does what you're asking for:

//A function for filling in a map variable with default values
@function defaultTo($mapVariable: (), $defaultMap){

    //if it's a map, treat each setting in the map seperately
    @if (type-of($defaultMap) == 'map' ){

        $finalParams: $mapVariable;

        // We iterate over each property of the defaultMap
        @each $key, $value in $defaultMap {

            // If the variable map does not have the associative key
            @if (not map-has-key($mapVariable, $key)) {

                // add it to finalParams
                $finalParams: map-merge($finalParams, ($key : $value));

            }
        }

        @return $finalParams;

    //Throw an error message if not a map
    } @else {
        @error 'The defaultTo function only works for Sass maps';
    }
}

Usage:

$map: defaultTo($map, (
    key1 : value1,
    key2 : value2
));

Then if you have a mixin for something, you can do this sort of thing:

@mixin someMixin($settings: ()){
    $settings: defaultTo($settings, (
        background: white,
        text: black
    );
    background: map-get($settings, background);
    color: map-get($settings, text);
}

.element {
    @include someMixin((text: blue));
}

Outputted CSS:

.element { background: white; color: blue; }

So you would use it like this based on what you said in the question:

$colors: defaultTo($colors, (
    text-color: #000,
    icon-color: #ccc,
));
Presbyopia answered 5/11, 2015 at 4:0 Comment(4)
Before a whole heap of people comment saying how you can do default values for mixins with "$var: value" when declaring the mixin; yes, I know that. I gave the $settings variable a default value of an empty list if you hadn't noticed. This was just the simplest way of showing something that is typically used in things that are far more complex.Presbyopia
Is this still the best way to get default functionality with maps? I went down the route of adding !default to the end of my map, thinking that would work.. After lots of head scratching, I realized I was following something that was incorrect and have lost a lot of time lolFeverish
With this technique, you need to add $colors: () !default; if you also want to handle the case if there's no override happeningTokoloshe
As far as I know, this is still the best way to do defaults in Sass maps.Presbyopia
R
9

Bootstrap has solved this issue as:

$grays: () !default;
// stylelint-disable-next-line scss/dollar-variable-default
$grays: map-merge(
  (
    "100": $gray-100,
    "200": $gray-200,
    "300": $gray-300,
    "400": $gray-400,
    "500": $gray-500,
    "600": $gray-600,
    "700": $gray-700,
    "800": $gray-800,
    "900": $gray-900
  ),
  $grays
);

https://github.com/twbs/bootstrap/blob/v4.1.3/scss/_variables.scss#L23

Rowan answered 29/5, 2020 at 21:22 Comment(1)
Very nice. Should be the accepted answer.Nell
A
0

The use of default in a map is incorrect.

It is not possible to set the default to a key value but to the entire map. That's why it doesn't work for you.

$colors: (
text-color: #000, 
background-color: #ccc) !default;

enter image description here

As you can see in the first image, step on the default value. In the second image, use the default map when you can't find another

enter image description here

Actiniform answered 19/6, 2023 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.