Daniel
Daniel

Reputation: 1155

How to create color shades using CSS variables similar to darken() of Sass?

I'm looking a way of modifying a CSS variable as you would in SCSS

Define a color like primary - and automatically I would get shades for focus and actives states. Basically, would like to change one variable in css variables and get 3 shades of the same color.

What Id like to achieve in CSS

$color-primary: #f00;

.button {
    background: $color-primary;

    &:hover,
    &:focus {
        background: darken($color-primary, 5%);
    }

    &:active {
        background: darken($color-primary, 10%);
    }
}

trying to achieve:

:root {
    --color-primary: #f00;
    --color-primary-darker: #f20000  //     var(--color-primary) * 5% darker
    --color-primary-darkest: #e50000 //     var(--color-primary) * 10% darker
}

.button {
    background: var(--color-primary);
}

.button:hover,
.button:focus {
    background: var(--color-primary-darker);
}

.button:active {
    background: var(--color-primary-darkest);
}

Upvotes: 76

Views: 82317

Answers (4)

Temani Afif
Temani Afif

Reputation: 273031

The new Specification introduces "relative color syntax" where you can do the following

:root {
  --color-primary: #f00; /* any format you want here */
  --color-primary-darker: hsl(from var(--color-primary) h s calc(l - 5));
  --color-primary-darkest: hsl(from var(--color-primary) h s calc(l - 10));
  
   background:
    linear-gradient(to right,var(--color-primary) 33%,var(--color-primary-darker) 0 66%,var(--color-primary-darkest) 0);

}

The idea is to convert the main color to hsl format and using calc() you adjust the lightness.

You can also use color-mix() and mix the color with black (or white) to create different shades from the same color.

html {
  --color-primary: #8A9B0F; 
  --color-primary-darker:  color-mix(in srgb,var(--color-primary),#000 15%);
  --color-primary-darkest: color-mix(in srgb,var(--color-primary),#000 30%);
  
  background:
    linear-gradient(to right,var(--color-primary) 33%,var(--color-primary-darker) 0 66%,var(--color-primary-darkest) 0);
}

I also wrote about it here : https://css-tip.com/color-shades-color-mix/


Old Answer

You can consider hsl() colors and simply control the lightness:

:root {
    --color:0, 100%; /*the base color*/
    --l:50%; /*the initial lightness*/
    
    --color-primary: hsl(var(--color),var(--l));
    --color-primary-darker: hsl(var(--color),calc(var(--l) - 5%));
    --color-primary-darkest: hsl(var(--color),calc(var(--l) - 10%)); 
}

.button {
    background: var(--color-primary);
    display:inline-block;
    padding:10px 20px;
    color:#fff;
    cursor:pointer;
}

.button:hover,
.button:focus {
    background: var(--color-primary-darker);
}

.button:active {
    background: var(--color-primary-darkest);
}
<span class="button">some text</span>

As a side note, darken() is also doing the same thing:

Makes a color darker. Takes a color and a number between 0% and 100%, and returns a color with the lightness decreased by that amount.

Upvotes: 162

user783388
user783388

Reputation:

Expanding on Temanis answer: I use a gradient - from black to a dynamic color to white - and expand the background 100 times. Now its only a question of positioning the background.

In the CSS

.dynamic-color {
    --lighten: 80%;
    --darken: 45%;
    --original-color: 50%;
    --color-intensity: var(--original-color);
    --color-variable: blue;
    background-image: linear-gradient(90deg,black, var(--color-variable),white);
    background-repeat: no-repeat;
    background-size: 10000% 100%;
    background-position-x: var(--color-intensity);
}

.dynamic-color:hover{
    --color-intensity: var(--lighten);
}

.dynamic-color.active{
    --color-intensity: var(--darken);
}

And in the HTML

<btn class="dynamic-color" style="--color-variable: green">Hover me</btn>

Upvotes: 1

Romalex
Romalex

Reputation: 1829

How about this (pure sass/scss):

First, we need to split a color into hsla values and save each one in a separate custom property. Luckily sass has some functions to do the job.

@mixin define-color($title, $color) {
    --#{$title}-h: #{hue($color)};
    --#{$title}-l: #{lightness($color)};
    --#{$title}-s: #{saturation($color)};
    --#{$title}-a: #{alpha($color)};
}

Now we can put it back together, making some adjustments on the way.

@function color($title, $hue: 0deg, $lightness: 0%, $saturation: 0%, $alpha: 0) {
    @return hsla(
        calc(var(--#{$title}-h) + #{$hue}), 
        calc(var(--#{$title}-s) + #{$saturation}),
        calc(var(--#{$title}-l) + #{$lightness}),
        calc(var(--#{$title}-a) + #{$alpha}),
    );
}

Now we are ready to define some color variables...

:root {
    @include define-color("primary", #696969);
    @include define-color("secondary", blue);
}

override them (to dynamically switch between themes for example)...

:root.theme-light {
    @include define-color("primary", #424242);
    @include define-color("secondary", red);
}

use and adjust them!

.example-class {
    color: color("primary");
    background: color("secondary", $lightness: +20%, $alpha: -0.3);
    border: 1px solid color("primary", $hue: -30deg, $saturation: 5%);
}

Upvotes: 6

Miguel Santo
Miguel Santo

Reputation: 85

If you are willing to take a different approach to your problem, using masks with the pseudo ":before" element would solve your problem. Although if you use this, i would advice you to put any content in the button inside a span or something, to give it a "z-index:1", so the content is not behind the mask.

:root {
    --color-primary: #f00;
}

.button {
    position:relative;
    background: var(--color-primary);

    &:before {
        content:'';
        position:absolute;
        width:100%;
        height:100%;
        top:0;
        left:0;
    }
}

.button:hover:before,
.button:focus:before {
    background:rgba(0,0,0,0.05) /* black mask with 5% opacity */
}

.button:active:before {
    background:rgba(0,0,0,0.1) /* black mask with 10% opacity */
}

Upvotes: 0

Related Questions