4

I'm trying to make a mixin that can detect whether to show dark text (if the colour passed into buttonStyles is light), or light text (for passing darker colours). I remember there was a way to do it with LESS and wondered whether there's a SASS way.

Consider this SCSS:

$white: #fff;
$text: #393939;
$font-std: 18px;
$transition-std: 0.4s ease all;
$primary: #f8e421;
$secondary: #354052;

@mixin buttonStyles($color) {
    font-size: $font-std;
    color: $text;
    padding: 1rem 3rem;
    background-color: $color;
    color: $white;
    transition: $transition-std;

    &:hover {
        cursor: pointer;
        background-color: lighten($color, 15%);
    }
    &:focus {
        background-color: lighten($color, 10%);
    }
    &:active {
        background-color: lighten($color, 25%);
    }
}
.btnPrimary {
    @include buttonStyles($primary);
}
.btnSecondary {
    @include buttonStyles($secondary);
}

And this html:

<button class='btnSecondary'>secondary</button>
<button class='btnPrimary'>primary</button>

The secondary button is far more legible than the primary button. I know that I could pass in a second argument that would set the text colour, but wondered whether there's a cleaner, automatic way as with LESS? (unfortunately I can't remember how it was done with LESS)

Live demo: https://jsfiddle.net/3v0ckeq9/5/

Thank you

EDIT:

I've added this function which almost seems to work:

@function ligthOrDark($color) {
    $result: red;

    @if (blackness($color) == 50) { // fails with > 50 or < 50
        $result: green;
    }

    @return $result;
}

But the issue is that SASS complains when trying to determine wether the colour is greater than 50 (or less than) but is ok with ==. I just want to be able to determine whether the color provided is dark or light so I can apply the right color of text.

Seems that there should be options to determine the darkness or lightness here: https://sass-lang.com/documentation/modules/color#grayscale

Alternative solutions are welcomed.

user8758206
  • 2,106
  • 4
  • 22
  • 45

2 Answers2

4

As per the documentation, whiteness and blackness are related to HWB color model. If you can work with HSL model then lightness can be used as follows:

$white: #fff;
$text: #393939;
$font-std: 18px;
$transition-std: 0.4s ease all;
$primary: #f8e421;
$secondary: #354052;

@function contrastText($color) {
    $result: invert($color);
    $lightness: lightness($result);
    @if ($lightness < 50) {
        $result: black;
    }
    @return $result;
}

@mixin buttonStyles($color) {
    font-size: $font-std;
    padding: 1rem 3rem;
    background-color: $color;
    color: contrastText($color);
    transition: $transition-std;

    &:hover {
        cursor: pointer;
        background-color: lighten($color, 15%);
    }
    &:focus {
        background-color: lighten($color, 10%);
    }
    &:active {
        background-color: lighten($color, 25%);
    }
}
.btnPrimary {
    @include buttonStyles($primary);
}
.btnSecondary {
    @include buttonStyles($secondary);
   
}
.btnTest {
    @include buttonStyles(#888);
}

After compilation it'll look like this: jsfiddle

/* CSS compiled from SASS*/
.btnPrimary {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: #f8e421;
  color: black;
  transition: 0.4s ease all;
}

.btnPrimary:hover {
  cursor: pointer;
  background-color: #faed6b;
}

.btnPrimary:focus {
  background-color: #faea52;
}

.btnPrimary:active {
  background-color: #fcf39d;
}

.btnSecondary {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: #354052;
  color: #cabfad;
  transition: 0.4s ease all;
}

.btnSecondary:hover {
  cursor: pointer;
  background-color: #536480;
}

.btnSecondary:focus {
  background-color: #495871;
}

.btnSecondary:active {
  background-color: #697d9e;
}

.btnTest {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: #888;
  color: black;
  transition: 0.4s ease all;
}

.btnTest:hover {
  cursor: pointer;
  background-color: #aeaeae;
}

.btnTest:focus {
  background-color: #a2a2a2;
}

.btnTest:active {
  background-color: #c8c8c8;
}

.testRed {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: red;
  color: cyan;
  transition: 0.4s ease all;
}

.testRed:hover {
  cursor: pointer;
  background-color: #ff4d4d;
}

.testRed:focus {
  background-color: #ff3333;
}

.testRed:active {
  background-color: #ff8080;
}

.testGreen {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: green;
  color: #ff7fff;
  transition: 0.4s ease all;
}

.testGreen:hover {
  cursor: pointer;
  background-color: #00cc00;
}

.testGreen:focus {
  background-color: #00b300;
}

.testGreen:active {
  background-color: lime;
}

.testBlue {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: blue;
  color: yellow;
  transition: 0.4s ease all;
}

.testBlue:hover {
  cursor: pointer;
  background-color: #4d4dff;
}

.testBlue:focus {
  background-color: #3333ff;
}

.testBlue:active {
  background-color: #8080ff;
}

.testOrange {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: orange;
  color: #005aff;
  transition: 0.4s ease all;
}

.testOrange:hover {
  cursor: pointer;
  background-color: #ffc04d;
}

.testOrange:focus {
  background-color: #ffb733;
}

.testOrange:active {
  background-color: #ffd280;
}

.testPurple {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: purple;
  color: #7fff7f;
  transition: 0.4s ease all;
}

.testPurple:hover {
  cursor: pointer;
  background-color: #cc00cc;
}

.testPurple:focus {
  background-color: #b300b3;
}

.testPurple:active {
  background-color: magenta;
}

.testYellow {
  font-size: 18px;
  padding: 1rem 3rem;
  background-color: yellow;
  color: blue;
  transition: 0.4s ease all;
}

.testYellow:hover {
  cursor: pointer;
  background-color: #ffff4d;
}

.testYellow:focus {
  background-color: #ffff33;
}

.testYellow:active {
  background-color: #ffff80;
}
<button class='btnSecondary'>secondary</button>
<button class='btnPrimary'>primary</button>
<button class='btnTest'>test</button>
<hr>
<button class='testRed'>Red</button>
<button class='testGreen'>Green</button>
<button class='testBlue'>Blue</button>
<button class='testOrange'>Orange</button>
<button class='testPurple'>Purple</button>
<button class='testYellow'>Yellow</button>


I couldn't figure out why HWB whiteness and blackness are not comparable with numbers like HSL lightness. But it may be possible that you can get them without these functions, because the formula is simple:
conversion
You can use red($color) and min max functions.


Using whitness, blackness, and lightness alone won't be sufficient with some colors. I think what you are looking for is relative luminance to get most contrasting color. White has luminance 1 and black has 0. And black-on-gray contrast is more than white-on-gray. To explore more figure out how this website is calculating contrast.


Optional arguments You can make arguments optional by defining default value for the parameter. In our case any non color value will do:

@function contrastText($color, $text:-1) {
    $result: invert($color);
    $lightness: lightness($result);
    @if ($lightness < 47) {
        $result: black;
    }
    @if ($lightness > 46) {
        $result: white;
    }
    @if (type_of($text) == 'color') {
        $result: $text;
    }
    @return $result;
}

@mixin buttonStyles($color) {
    color: contrastText($color);
   /*color: contrastText($color,);*/
   /*color: contrastText($color, red);*/
}
the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • thanks - looks like this might be the closest possible options. But is it possible to pass in optional arguments with SASS functions? I'd like to be able to pass in an optional text variable as the yellow button is a bit off, like in this demo I just made: https://jsfiddle.net/xytuj739/ - is that possible? Line 17 seems to break it – user8758206 Dec 25 '21 at 00:15
  • 1
    Yes, It is failing at line 27 `color: contrastText($color)` due to missing parameter. You can make arguments optional. See the updated answer. – the Hutt Dec 25 '21 at 04:36
  • that's great, thanks! – user8758206 Dec 25 '21 at 12:20
0

I've added another solution. To see it run: https://jsfiddle.net/gu2r4jqk/7/

function theFunction(){
    console.log("woho");
}
$--color: #0084ff;
$--black: black;
$--white: white;
$--background-active: ligther($--color, 30%);

body {
  display: flex;
  gap: 5px;
  align-items: center;
  height: 100vh;
  justify-content: center;
  margin: 0;
}

@mixin backgroundLigthen($--color, $--percentage) {
  background-color: lighten($--color, $--percentage);
  $--background: lighten($--color, $--percentage);
  @include adjust-text-color($--background);
}

@mixin backgroundDarken($--color, $--percentage) {
  background-color: darken($--color, $--percentage);
  $--background: darken($--color, $--percentage);
  @include adjust-text-color($--background);
}

@mixin adjust-text-color($--background) {
  @if (lightness($--background) < 50%) {
    color: $--white;
  } @else {
    color: $--black;
  }
}

.button1 {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px;
  border-radius: 130px;
  cursor: pointer;
  font-weight: 500;
  font-size: 50px;
  font-family: "Lexend", sans-serif;
  user-select: none;
  background-color: $--color;
  transition: background-color 0.2s ease;
  @if (lightness($--color) > 50%) {
    color: $--white;
  }

  &.selected {
    @include backgroundDarken($--color, 5%);
  }

  &:hover {
    @include backgroundDarken($--color, 10%);
  }

  &:active {
    @include backgroundLigthen($--color, 30%);
  }
}

.text {
  text-align: center;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Perfect Button</title>
  <link rel="stylesheet" href="index.css" />

  <link href="https://fonts.googleapis.com/css2?family=Lexend:wght@100;200;300;400;500;600;700;800;900&display=swap"
    rel="stylesheet" />
</head>

<body>
  <div class="button1" onclick="theFunction()">
    <div class="text">Click Me</div>
  </div>

  <div class="button1 selected" onclick="theFunction()">
    <div class="text">Click Me</div>
  </div>

  <script src="index.js"></script>
</body>

</html>
Sam
  • 124
  • 10