521

I am designing an app in electron, so I have access to CSS variables. I have defined a color variable in vars.css:

:root {
  --color: #f0f0f0;
}

I want to use this color in main.css, but with some opacity applied:

#element {
  background: (somehow use var(--color) at some opacity);
}

How would I go about doing this? I am not using any preprocessor, only CSS. I would prefer an all-CSS answer, but I will accept JavaScript/jQuery.

I cannot use opacity because I am using a background image that should not be transparent.

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
jotch
  • 5,416
  • 2
  • 12
  • 20

18 Answers18

732

You can't take an existing color value and apply an alpha channel to it. Namely, you can't take an existing hex value such as #f0f0f0, give it an alpha component and use the resulting value with another property.

However, custom properties allow you to convert your hex value into an RGB triplet for use with rgba(), store that value in the custom property (including the commas!), substitute that value using var() into an rgba() function with your desired alpha value, and it'll just work:

:root {
  /* #f0f0f0 in decimal RGB */
  --color: 240, 240, 240;
}

body {
  color: #000;
  background-color: #000;
}

#element {
  background-color: rgba(var(--color), 0.8);
}
<p id="element">If you can see this, your browser supports custom properties.</p>

This seems almost too good to be true.1 How does it work?

The magic lies in the fact that the values of custom properties are substituted as is when replacing var() references in a property value, before that property's value is computed. This means that as far as custom properties are concerned, the value of --color in your example isn't a color value at all until a var(--color) expression appears somewhere that expects a color value (and only in that context). From section 2.1 of the css-variables spec:

The allowed syntax for custom properties is extremely permissive. The <declaration-value> production matches any sequence of one or more tokens, so long as the sequence does not contain <bad-string-token>, <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>, or top-level <semicolon-token> tokens or <delim-token> tokens with a value of "!".

For example, the following is a valid custom property:

--foo: if(x > 5) this.width = 10;

While this value is obviously useless as a variable, as it would be invalid in any normal property, it might be read and acted on by JavaScript.

And section 3:

If a property contains one or more var() functions, and those functions are syntactically valid, the entire property’s grammar must be assumed to be valid at parse time. It is only syntax-checked at computed-value time, after var() functions have been substituted.

This means that the 240, 240, 240 value you see above gets substituted directly into the rgba() function before the declaration is computed. So this:

#element {
  background-color: rgba(var(--color), 0.8);
}

which doesn't appear to be valid CSS at first because rgba() expects no less than four comma-separated numeric values, becomes this:

#element {
  background-color: rgba(240, 240, 240, 0.8);
}

which, of course, is perfectly valid CSS.

Taking it one step further, you can store the alpha component in its own custom property:

:root {
  --color: 240, 240, 240;
  --alpha: 0.8;
}

and substitute it, with the same result:

#element {
  background-color: rgba(var(--color), var(--alpha));
}

This allows you to have different alpha values that you can swap around on-the-fly.


1 Well, it is, if you're running the code snippet in a browser that doesn't support custom properties.

Community
  • 1
  • 1
BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • 2
    @Roberrrt: It's something I should have realized early on, in fact, seeing as I posted [these](http://stackoverflow.com/questions/37442603/how-to-use-commas-in-a-css-variable-fallback/37443195#37443195) [answers](http://stackoverflow.com/questions/38751778/is-it-possible-to-use-css-custom-properties-in-values-for-the-content-property/38751906#38751906) previously. – BoltClock Dec 21 '16 at 14:48
  • Still though, this is really useful, would the same principle be possible with SCSS/Less/Sass? – roberrrt-s Dec 21 '16 at 14:48
  • 2
    If we're going this var, why not use something like: `.element2 { background-color: rgba(var(--red), var(--low-opacity); }`. This way you can fully utilise the usage of variables :). – roberrrt-s Dec 21 '16 at 14:51
  • @Roberrrt postcss has some useful fallback plugins – Ced Aug 27 '17 at 21:10
  • 37
    Unfortunately, the value `"240, 240, 240"` is not editable with a color picker. That is a huge miss when you need to find the right colors for your GUI. – GetFree Sep 16 '17 at 07:43
  • @GetFree: Yeah, I'm not surprised. Sadly I don't know of a way around that that's compatible with the var() grammar. – BoltClock Oct 30 '19 at 17:20
  • 1
    Unbelievable, not working for me on latest chrome. Identifies as a valid css property but doesn't work. – m4heshd Jan 23 '20 at 15:37
  • 15
    rgba() is nowadays a synonym for rgb() so you can use `color: rgb(var(--color), .4)`. But the reason I found this SOQ is because the following **doesn't** work: `:root { --hex-color: "#333333"; } element { background: var(--hex-color)99 /*this should be alpha 60%*/ }`, but somehow the two strings are not joined into `#33333399` which would work. – s3c Feb 17 '20 at 10:10
  • 14
    @s3c The syntax `var(--hex-color)99` is converted to two tokens `#333333 99` (notice the space to separate tokens) which obviously is not the thing you want. Custom properties were originally defined to copy tokens, not strings and this is the end result. It's way too late to fix this now. – Mikko Rantalainen Feb 19 '20 at 06:43
  • @Mikko Rantalainen - So that's what happens... Thanks! A shame, but not something to get stressed about though. I solved it with a `hex2rgb()` function e.g. `hex2rgb("ff0000") == "255, 0, 0"` so now `:root { --rgb-color: "=hex2rgb(#333333)?>"; } element { background: rgb(var(--rgb-color), .6) }` – s3c Feb 19 '20 at 08:12
  • 14
    @s3c: The good news is that CSS Color 5 is introducing some cool new functions to manipulate existing hex values, including changing their alpha channel: https://drafts.csswg.org/css-color-5/#colormodify – BoltClock Mar 10 '20 at 03:17
  • @Mikko Rantalainen: See my reply to s3c. – BoltClock Mar 10 '20 at 03:17
  • 1
    This technique doesn't work when used like this: `box-shadow: 0 0 10px 0 rgba(var(--color-accent), 0.2);` though. – Dai May 20 '20 at 07:09
  • @Dai: The following works for me, regardless of where in the ancestor chain I define --color-accent: `data:text/html,

    Test

    ` Check to see if --color-accent is being inherited correctly by the element with the shadow, or if the syntax of the custom property value is correct.
    – BoltClock May 20 '20 at 07:13
  • @BoltClock Ah, I found the problem - my `--color-accent` was a hex color, not an `r,g,b` color. – Dai May 28 '20 at 23:57
  • 3
    @BoltClock Based on what you said, then why can I not do this: `:root { --color: #000000; background: var(--color)88; }` in order to add the opacity component that way. Based on what you said, I would expect background to compute to `background: #00000088;` but it doesn't work for me in Chrome. – trusktr Aug 19 '20 at 21:53
  • @trusktr: See Mikko's reply to s3c above. – BoltClock Aug 20 '20 at 05:45
  • Ah ok. Might be worth making that a sidenote in the answer. Wish I knew why spec authors chose the less-intuitive options. – trusktr Aug 21 '20 at 21:53
  • 2
    Noting this works for hsl into hsla too: https://codepen.io/urlyman/pen/eYzqydY. – Jonathan Schofield Nov 23 '20 at 17:38
  • 1
    Looks like CanIUse doesn't yet have CSS Color Module Level 5 (as of Nov. 2020) but you can vote for its inclusion here: https://github.com/Fyrd/caniuse/issues/5405 – Roy Tinker Nov 23 '20 at 22:18
  • 1
    Unfortunately, we cannot simply specify `color: var(--color)` in this way. No way is beautiful enough for both rgb and non-rgb syntaxes. – Eric Mar 04 '21 at 08:21
  • "custom properties allow you to convert your hex value into an RGB triplet" How would that work? As far as I can see, this solution only works with rgb values stored in css custom properties. Is there a way to get it work if all theming variables are custom properties in hex only? – Andre Brdoch Jun 01 '21 at 09:46
  • 1
    @Andre Brdoch: Yeah, not the best wording on my part. You'd need to convert it manually, then specify the RGB triplet as a custom property. – BoltClock Jun 01 '21 at 12:12
  • 2
    I try it and it didn't work for me. Anyone else getting the same results? – Royer Adames Jul 26 '21 at 16:21
  • @GetFree If you are using Less, you can use Mixin to preprocess rgb string to pure numbers to solve this problem, see my example workaround: https://github.com/upupming/vue3-compact-template/blob/9405f655c70cc851433a22204133c07dcb452c5a/src/stories/ThemedExample.vue#L96-L107 – upupming Nov 10 '21 at 17:13
  • 4
    @MikkoRantalainen I don't think it's too late to fix it now. I'm 100% sure that there are tons of neat solution to this issue, especially taken globally. It makes sense to store the main colors in CSS variables which are used in the rest of the stylesheets and to not having to create new variables for each intensity of the same color. I'm flabbergasted, it's 2022, and they haven't provided a proper solution to this despite the obvious need and popularity. Is there a place where I can vote for this issue? – Samuel Gfeller Apr 13 '22 at 12:28
  • @SamuelGfeller CSS is defined to have both backwards compatibility and forwards compatibility which prevents the syntax a lot. The current limitations set for the CSS custom variables directly follows this decision. A new syntax that's compatible with CSS forward compatible parsing rules would be needed. As nice as it would be to fix the old mistakes, the spec has already given a promise that existing syntax will stay. – Mikko Rantalainen Apr 13 '22 at 14:30
  • This is so clunky. Maintaining 3 variables per color. People use hex codes not RGB. Most common use case is add alpha to a hex code color. Wish the people who did the CSS spec tried to make a real website some time beyond a word document. – run_the_race May 27 '22 at 13:02
  • @run_the_race: Keep in mind that variables weren't designed with this specific use case in mind. This is just something I came up with for want of a better solution (which will soon be available in css-color-5). I can't speak to the popularity of hex colors since I don't intuitively think in hexadecimal and would rather not waste my time converting back and forth, but hopefully the new features will make my solution obsolete. – BoltClock May 30 '22 at 09:42
  • I've tried this with the new `rgb(0 0 0 / 15%)` syntax, `rgb(var(--neutral-8)/ 15%)` is not working for me in the latest chrome. https://css-tricks.com/no-comma-color-functions-in-css/ – Shanimal Sep 15 '22 at 21:03
112

color-mix

caniuse support table

color-mix is already well-supported syntax which allows a great deal of control:

html {
  font: 700 6em Arial;
  color: Salmon;
  background: color-mix(in srgb, currentColor 20%, transparent);
}
AWESOME

html {
  background: repeating-linear-gradient(135deg, lightblue 0, lightblue 10%, transparent 0, transparent 50%);
  background-size: 3em 3em;

  display: grid;
  place-items: center;
  height: 100vh;
}

body::before, body::after {
  content: '';
  display: inline-block;
  padding: 10vw;
  border-radius: 50%;
}


body::before {
  --color: red;
  
  translate: 20% 0;
  background: color-mix(in srgb, var(--color) 70%, transparent);
}

body::after {
  --color: green;
  --transparency: 20%;
  --color-with-transparency: color-mix(in srgb, var(--color) var(--transparency, 100%), transparent);
  
  translate: -20% 0;
  background: var(--color-with-transparency);
}

Relative color syntax

caniuse support table

With this new CSS ability (css-color-5) which allows color format transformations, it also will also allow adding opacity to any color in any format, for example, to RGB (relative transformations can be done to any other format):

html { --color: blue }
.with-opacity { background: rgb(from var(--color) r g b / 50%) }

(As of writing, not yet available in browsers. Will update once arrives)

Codepen demo

vsync
  • 118,978
  • 58
  • 307
  • 400
  • 12
    This can't come soon enough. Its like the people who make the spec never make actual websites, where there are themes and effects, linked to a certain color. – run_the_race Oct 27 '22 at 19:39
  • Another related game changer for color themes is [contrast-color](https://drafts.csswg.org/css-color-6/#colorcontrast), will be so great once they are adopted. – run_the_race Oct 31 '22 at 09:05
  • is there a link to https://caniuse.com for this? Please update the answer with this info if available – Dmitry Efimenko Jan 06 '23 at 15:00
  • @DmitryEfimenko - There is [not](https://github.com/Fyrd/caniuse/issues/5405). – vsync Jan 06 '23 at 15:04
  • 1
    thanks for the link to the issue. +1-ed – Dmitry Efimenko Jan 06 '23 at 15:47
  • 3
    It's now available on caniuse.com: https://caniuse.com/css-relative-colors – Regaddi May 11 '23 at 12:41
  • 1
    I already added the color-mix() answer. It's a bit unfair to edit your answer to also add it. Now my answer looks a bit useless and a copy of yours ... we are supposed to have *multiple useful* answers – Temani Afif Jun 18 '23 at 18:37
  • @TemaniAfif - I know you and I knew you specifically gonna say that. Ho Temani... you are so... you. I did not see your answer and I came back from vacation and read the news about `color-mix` and simply went straight to update my answer. That is a fair thing to do. I constantly update all of my hundreds of answers, everyday, because I care about the **extra** value my specific answers bring. I will not undo my edit, sorry, you cannot actually expect me look like a fool not mentioning something as trivial as `color-mix` in my answer.. I cannot live with that shame – vsync Jun 18 '23 at 19:40
  • @TemaniAfif - Next time lets team up and try to do something together :) I know you are a super talented guy – vsync Jun 18 '23 at 19:43
54

I know the OP isn't using a preprocessor, but I would have been helped if the following information was part of the answer here (I can't comment yet, otherwise I would have commented @BoltClock answer.

If you are using, e.g. scss, the answer above will fail, because scss attempts to compile the styles with a scss-specific rgba()/hsla() function, which requires 4 parameters. However, rgba()/hsla() are also native css functions, so you can use string interpolation to bypass the scss function.

Example (valid in sass 3.5.0+):

:root {
    --color_rgb: 250, 250, 250;
    --color_hsl: 250, 50%, 50%;
}

div {
    /* This is valid CSS, but will fail in a scss compilation */
    background-color: rgba(var(--color_rgb), 0.5);
    
    /* This is valid scss, and will generate the CSS above */
    background-color: #{'rgba(var(--color_rgb), 0.5)'};
}
<div></div>

Note that string interpolation will not work for non-CSS scss functions, such as lighten(), because the resulting code would not be functional CSS. It would still be valid scss though, so you would receive no error in compilation.

SimplyPhy
  • 995
  • 1
  • 8
  • 14
  • 5
    If you prefer to use native CSS color functions in your Sass .scss files, you can include the following function definitions at the top of your file to override Sass' handling and make them pass through: `@function rgb($args...) { @return #{'rgb(#{$args})'}; }` `@function rgba($args...) { @return #{'rgba(#{$args})'}; }` `@function hsl($args...) { @return #{'hsl(#{$args})'}; }` `@function hsla($args...) { @return #{'hsla(#{$args})'}; }` ```` – lunelson Oct 16 '18 at 11:32
  • `rgba` is a synonym for `rgb` for quite some time now.. You are hence allowed to drop the "a". – s3c Feb 19 '20 at 08:14
  • 6
    Another workaround for scss files is to use uppercase (`RGB`) which is then ignored by sass. Eg: `color: RGB(var(--color_rgb), 0.5);`. From [GitHub](https://github.com/sass/node-sass/issues/2251#issuecomment-365861645) – Jono Job Apr 17 '20 at 02:35
  • 1
    Nice answer! If you have already defined the colors in hex, you can simply add this code to convert it to the custom rgb properties: `:root { @each $color, $value in $colors { --#{$color}_rgb: #{red($value), green($value), blue($value)}; } }` – Torsten Kolb Jul 31 '20 at 10:38
  • 2
    Using the latest `sass` package on npm, this workaround doesn't appear necessary anymore – daviestar Apr 29 '21 at 10:21
  • As per node-sass v7.0.1 it seems that either workaround is still necessary – zoltankundi Jun 21 '22 at 02:37
44

I was in a similar situation, but unfortunately the given solutions did not work for me, as the variables could be anything from rgb to hsl to hex or even color names.
I solved this issue now, by applying the background-color and the opacity to a pseudo :after or :before element:

.container {
    position: relative;
}

.container::before {
    content: "";
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    background-color: var(--color);
    opacity: 0.3;
}

The styles might need to be changed a little, depending on the element the background should be applied to.
Also it might not work for all situations, but hopefully it helps in some cases, where the other solutions can't be used.

Edit: I just noticed, that this solution obviously also impacts the text color, as it creates an element in front of the target element and applies a transparent background color to it.
This might be a problem in some cases.

vsync
  • 118,978
  • 58
  • 307
  • 400
Robert P
  • 9,398
  • 10
  • 58
  • 100
  • 1
    This not only has the advantage of allowing more flexible specification of the color (e.g., a name, or `rgb` or `HSL`) but also avoids any conflict between native CSS color functions and Sass's color functions. See [SimplyPhy's answer](https://stackoverflow.com/a/47625605/8401379) below. – Jim Ratliff Sep 06 '19 at 07:34
  • 7
    I think it's better to use `:before` so you get the right stacking order without playing with `z-index`. – Mikko Rantalainen Feb 19 '20 at 06:46
  • @MikkoRantalainen You'll still have to use `z-index: -1` because [positioned elements cover non-positioned by default](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/Stacking_without_z-index). – EvgenKo423 Feb 21 '23 at 06:58
  • @EvgenKo423 You're right that it must be considered. Because `::before` is technically *inside* the `.container` it's rendered on top of the `.container` unless `z-index` is negative. However, if `.container` has elements with `position: relative`, those will be rendered above the `::before` pseudo-element even with default `z-index` values. – Mikko Rantalainen Feb 21 '23 at 10:20
10

SCSS / SASS

Advantage: You can just use Hex color values, instead to use the 8 Bit for every channel (0-255).

This is how I did it with the initial idea of: https://codyhouse.co/blog/post/how-to-combine-sass-color-functions-and-css-variables

Edit: You could also modify the alpha function to just use #{$color-name}-rgb and omit the generated *-r, *-g, *-b CSS variables.


Result

body {
  --main-color: rgb(170, 68, 204);
  --main-color-rgb: 170,68,204;
  --main-color-r: 170;
  --main-color-g: 68;
  --main-color-b: 204;
}

.button-test {
  // Generated from the alpha function
  color: rgba(var(--main-color-r), var(--main-color-g), var(--main-color-b), 0.5);
  // OR (you wrote this yourself, see usage)
  color: rgba(var(--main-color-rgb), 0.5);
}

Usage:

body {
    @include defineColorRGB(--main-color, #aa44cc);
}

.button-test {
  // With alpha function:
  color: alpha(var(--main-color), 0.5);
  // OR just using the generated variable directly
  color: rgba(var(--main-color-rgb), 0.5);
}

Mixin and functions

@mixin defineColorRGB($color-name, $value) {
    $red: red($value);
    $green: green($value);
    $blue: blue($value);
    #{$color-name}: unquote("rgb(#{$red}, #{$green}, #{$blue})");
    #{$color-name}-rgb: $red,$green,$blue;
    #{$color-name}-r: $red;
    #{$color-name}-g: $green;
    #{$color-name}-b: $blue;
}

// replace substring with another string
// credits: https://css-tricks.com/snippets/sass/str-replace-function/
@function str-replace($string, $search, $replace: '') {
    $index: str-index($string, $search);
    @if $index {
        @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
    }
    @return $string;
}

@function alpha($color, $opacity) {
    $color: str-replace($color, 'var(');
    $color: str-replace($color, ')');
    $color-r: var(#{$color+'-r'});
    $color-g: var(#{$color+'-g'});
    $color-b: var(#{$color+'-b'});
    @return rgba($color-r, $color-g, $color-b, $opacity);
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
Stefan Rein
  • 8,084
  • 3
  • 37
  • 37
10

If you use dark and light mode, i use this sample. I prefer separate between colors and rgb colors variable assignment. So i use two each loop. I realise this solution is not dry code. If you want to dry code could you use one loop.

$colors-light: (
  white: #fff,
  black: #0c0d0e,
  orange: #f48024,
  green: #5eba7d,
  blue: #0077cc,
  red: #d1383d,
  red-100: #e2474c,
  red-200: red,
);

$colors-dark: (
  black: #fff,
  white: #2d2d2d,
  orange: #dd7118,
  green: #5eba7d,
  blue: #0077cc,
  red: #aa1c21,
  red-100: #c9292e,
  red-200: red,
);

@function hexToRGB($hex) {
  @return red($hex), green($hex), blue($hex);
}

@mixin generate_colors($colors) {
  // Colors
  @each $color, $value in $colors {
    @if str-slice(#{$value}, 1, 1) == "#" {
      --#{$color}: #{$value};
    } @else {
      --#{$color}: var(--#{$value});
    }
  }

  // RGB Colors
  @each $color, $value in $colors {
    @if str-slice(#{$value}, 1, 1) == "#" {
      --RGB_#{$color}: #{hexToRGB($value)};
    } @else {
      --RGB_#{$color}: var(--RGB_#{$value});
    }
  }
}

:root {
  @include generate_colors($colors-light);
}

[data-theme="dark"] {
  @include generate_colors($colors-dark);
}

dry code

@mixin generate_colors($colors) {
  // Colors, RGB Colors
  @each $color, $value in $colors {
    @if str-slice(#{$value}, 1, 1) == "#" {
      --#{$color}: #{$value};
      --RGB_#{$color}: #{hexToRGB($value)};
    } @else {
      --#{$color}: var(--#{$value});
      --RGB_#{$color}: var(--RGB_#{$value});
    }
  }
}

css Output

:root {
  --white: #fff;
  --RGB_white: 255, 255, 255;
  --black: #0c0d0e;
  --RGB_black: 12, 13, 14;
  --orange: #f48024;
  --RGB_orange: 244, 128, 36;
  --green: #5eba7d;
  --RGB_green: 94, 186, 125;
  --blue: #0077cc;
  --RGB_blue: 0, 119, 204;
  --red: #d1383d;
  --RGB_red: 209, 56, 61;
  --red-100: #e2474c;
  --RGB_red-100: 226, 71, 76;
  --red-200: var(--red);
  --RGB_red-200: var(--RGB_red);
}

[data-theme="dark"] {
  --black: #fff;
  --RGB_black: 255, 255, 255;
  --white: #2d2d2d;
  --RGB_white: 45, 45, 45;
  --orange: #dd7118;
  --RGB_orange: 221, 113, 24;
  --green: #5eba7d;
  --RGB_green: 94, 186, 125;
  --blue: #0077cc;
  --RGB_blue: 0, 119, 204;
  --red: #aa1c21;
  --RGB_red: 170, 28, 33;
  --red-100: #c9292e;
  --RGB_red-100: 201, 41, 46;
  --red-200: var(--red);
  --RGB_red-200: var(--RGB_red);
}

body {
  background-color: var(--white);
}

.colors {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: horizontal;
  -webkit-box-direction: normal;
      -ms-flex-direction: row;
          flex-direction: row;
  -ms-flex-wrap: wrap;
      flex-wrap: wrap;
  -webkit-box-pack: start;
      -ms-flex-pack: start;
          justify-content: flex-start;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  margin: 50px 0 0 30px;
}

.box {
  width: 100px;
  height: 100px;
  margin-right: 5px;
}

.black {
  background-color: var(--black);
}

.white {
  background-color: var(--white);
}

.orange {
  background-color: var(--orange);
}

.green {
  background-color: var(--green);
}

.blue {
  background-color: var(--blue);
}

.red {
  background-color: var(--red);
}

.red-200 {
  background-color: var(--red-200);
}

.black-rgba {
  background-color: rgba(var(--RGB_black), 0.5);
}

.white-rgba {
  background-color: rgba(var(--RGB_white), 0.5);
}

.orange-rgba {
  background-color: rgba(var(--RGB_orange), 0.5);
}

.green-rgba {
  background-color: rgba(var(--RGB_green), 0.5);
}

.blue-rgba {
  background-color: rgba(var(--RGB_blue), 0.5);
}

.red-rgba {
  background-color: rgba(var(--RGB_red), 0.5);
}

.red-rgba-200 {
  background-color: rgba(var(--RGB_red-200), 0.5);
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div>
      <input type="checkbox" id="dark-switch" name="theme" />
      <label for="dark-switch">Dark / Light</label>
    </div>

    <div class="color-box">
        <div class="colors">
          <div class="box red-200"></div>
          <div class="box black"></div>
          <div class="box white"></div>
          <div class="box orange"></div>
          <div class="box green"></div>
          <div class="box blue"></div>
          <div class="box red"></div>
        </div>
        <br>

        <h1>RGBA</h1>
        <div class="colors">
          <div class="box red-rgba-200"></div>
          <div class="box black-rgba"></div>
          <div class="box white-rgba"></div>
          <div class="box orange-rgba"></div>
          <div class="box green-rgba"></div>
          <div class="box blue-rgba"></div>
          <div class="box red-rgba"></div>
        </div>

    </div>

    <script>
      const dark_switch = document.getElementById("dark-switch");

      dark_switch.addEventListener("change", (e) => {
        e.target.checked
          ? document.documentElement.setAttribute("data-theme", "dark")
          : document.documentElement.setAttribute("data-theme", "light");
      });
    </script>
  </body>
</html>
  • 2
    This was an incredible post! Thanks for all this code. I basically copied it all into my own project. For anyone coming here who does have issues, I did have to change one thing. When initially declaring the SASS color variables, you need to wrap quotes around the names like so: `$colors-light: ( 'color-primary': #2F302F, 'color-primary-variant': #4E4E4E,` – dmikester1 Apr 10 '21 at 05:14
  • 2
    Unfortuntely, this isn't pure CSS as requested. – mmarlow Jun 17 '22 at 08:55
  • Then you have an ever bigger problem, SASS, which won't be run time dynamic – run_the_race Oct 27 '22 at 19:40
8

Here is an new answer:

use cross-fade:

background-image: cross-fade(linear-gradient(0, red, red), linear-gradient(0, transparent, transparent), 0.5)
  1. linear-gradient(0, red, red) is your color as image
  2. linear-gradient(0, transparent, transparent) is an transparent image
  3. 0.5 is the opacity you need to config

enter image description here


The Old Answer (No Recommand): you can use linear-gradient to hack the color:

background: linear-gradient(to bottom, var(--your-color) -1000%, var(--mixin-color), 1000%)

$(() => {
  const setOpacity = () => {
    $('#canvas').css('--opacity', $('#opacity-value').val())
  }
  const setColor = () => {
    $('#canvas').css('--color', $('#color-value').val());
  }
  $('#opacity-value').on('input', setOpacity);
  $('#color-value').on('input', setColor);
  setOpacity();
  setColor();
})
#canvas {
  width: 100px;
  height: 100px;
  border: 2px solid #000;
  --hack: 10000%;
  background: linear-gradient( to bottom, var(--color) calc((var(--opacity) - 1) * var(--hack)), transparent calc(var(--opacity) * var(--hack)));
}

#container {
  background-image: linear-gradient(45deg, #b0b0b0 25%, transparent 25%), linear-gradient(-45deg, #b0b0b0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #b0b0b0 75%), linear-gradient(-45deg, transparent 75%, #b0b0b0 75%);
  background-size: 20px 20px;
  background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
  padding: 10px;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="container">
  <div id="canvas"></div>
</div>
<hr/>
<input type="range" id="opacity-value" min="0" max="1" step="0.1" value="0.5" />
<input type="color" id="color-value" />
user2053153
  • 196
  • 1
  • 5
  • This solution badly lacks an explanation and has **2 big downsides: 1. You basically create an image 100 times bigger than your element** (the snippet uses different values). And to be precise you actually need to make it 256 times bigger (because there are 256 shades for each 8-bit component) and somehow move it by 100% intervals corresponding to a percent value with 100% shift to the right; **2. It also darkens the color itself** as it makes a gradient not only for opacity, but for color as well. So, while being a nice hack, it would be a terrible solution which is hard to get right. – EvgenKo423 Feb 21 '23 at 09:32
  • for example `--mixin-color` is `transparent`, so your-color will linear-gradient to transparent. as long as there is enough space for the two colors to transition, the human eye will not be able to see that they are gradual. So you can switch between your-color and transparent by simply moving `background-position` – user2053153 Mar 09 '23 at 03:34
7

You can consider color-mix() and mix your color with a transparent one:

html {
  --color: #8A9B0F; /* the main color */
  
  /* adjust the percentage of transparency by changing the 25% */
  --new-color: color-mix(in srgb,var(--color),#0000 25%);
  
  /* to illustrate */
  background: linear-gradient(90deg,var(--color) 50%,var(--new-color) 0);
}

Related: https://css-tip.com/color-shades-color-mix/

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • 1
    Support is not great https://caniuse.com/mdn-css_types_color_color-mix – Tofandel Jun 14 '23 at 14:05
  • @Tofandel do you know how to read that page? because you are showing me that it's Geen in all the major browser which means a great support – Temani Afif Jun 14 '23 at 14:32
  • 1
    Yes, what matters is `Global 78.81%` most mobile browsers do not support that, and older popular browsers, unless it's `>95%` do not use it without a fallback. If you look at all the earliest browser versions that supports it, was released in March 2023, not everybody updated yet, hence the poor global support It's not just green in all latest versions of major browsers that matter, it's how old they are and adoption of those versions. But it's a good sign, and probably by end of year support will be over `90%` – Tofandel Jun 14 '23 at 15:19
  • @Tofandel Well, your comment confirms that the support is actually great and that it's ready to be used in production. As side note, many properties have a usage under `95%` and are widely used without fallback (gap: https://caniuse.com/mdn-css_properties_gap_flex_context / container: https://caniuse.com/mdn-css_properties_container / rotate: https://caniuse.com/mdn-css_properties_rotate and more). We don't wait for 95% of 90% to use a feature. – Temani Afif Jun 14 '23 at 15:53
  • 2
    No, it's the opposite, it's too recent to be used in production, but it's safe to use in a dev environment. Over `91%` is still pretty good and is safe to be used in production but `78%` is too low. The main point is too check how your website looks in one of those outdated browsers and see if it's not too broken or needs a fallback (which for a background it will, but for some flex gap it won't) – Tofandel Jun 14 '23 at 16:04
6

This is indeed possible with CSS. It's just a bit dirty, and you'll have to use gradients. I've coded a small snippet as example, take note that for dark backgrounds, you should use the black opacity, as for light- the white ones.:

:root {
  --red: rgba(255, 0, 0, 1);
  --white-low-opacity: rgba(255, 255, 255, .3);
  --white-high-opacity: rgba(255, 255, 255, .7);
  --black-low-opacity: rgba(0, 0, 0, .3);
  --black-high-opacity: rgba(0, 0, 0, .7);
}

div {
 width: 100px;
 height: 100px;
 margin: 10px;
}
    
    
.element1 {
 background: 
        linear-gradient(var(--white-low-opacity), var(--white-low-opacity)) no-repeat,
 linear-gradient(var(--red), var(--red)) no-repeat;
}

.element2 {
 background: 
        linear-gradient(var(--white-high-opacity), var(--white-high-opacity)) no-repeat,
 linear-gradient(var(--red), var(--red)) no-repeat;
}
    
.element3 {
 background: 
        linear-gradient(var(--black-low-opacity), var(--black-low-opacity)) no-repeat,
 linear-gradient(var(--red), var(--red)) no-repeat;
}

.element4 {
 background: 
        linear-gradient(var(--black-high-opacity), var(--black-high-opacity)) no-repeat,
 linear-gradient(var(--red), var(--red)) no-repeat;
}
<div class="element1">hello world</div>
<div class="element2">hello world</div>
<div class="element3">hello world</div>
<div class="element4">hello world</div>
roberrrt-s
  • 7,914
  • 2
  • 46
  • 57
  • You do not need to specify background-size - gradients have no intrinsic size and will automatically stretch as a result. – BoltClock Dec 21 '16 at 13:50
  • @BoltClock Yeah, I literally thought of that when I posted it, it was just a bit of playing around in the codepen ;). Cleaned up now, thanks! – roberrrt-s Dec 21 '16 at 13:51
  • This is clever, I had not thought of layering solid-color gradients over one another when I answered a [similar question](http://stackoverflow.com/questions/29591465/use-css-variables-with-rgba-for-gradient-transparency) last year. This question is probably more general anyway the way it was written, the one I answered was for a very specific use case. – BoltClock Dec 21 '16 at 13:53
  • It doesn't really work when the backgrounds are different though, I now assume a white background (255,255,255) when applying the 'opacity'. It could possibly be defaulted to OP's main background color. But then again, white background will probably fit the need of most lighter colors to the extend that people will not notice this. – roberrrt-s Dec 21 '16 at 13:54
  • If it provides the OP with an answer, then why not? I've also included the dark/light explanation. – roberrrt-s Dec 21 '16 at 13:58
  • 1
    I just discovered something else that's pretty incredible. I've now posted an answer. – BoltClock Dec 21 '16 at 14:39
0
:root{
--color: 255, 0, 0;
}

#element{
    background-color: rgba(var(--color), opacity);
}

where you replace opacity with anything between 0 and 1

  • 1
    Is this an attempt at answering the question? Because if so, the code doesn't really make sense. Particularly the `rgba(var(--color), opacity)` bit. Especially since your custom property value is the entire rgb() notation. But also because of the "opacity" keyword. – BoltClock Dec 22 '16 at 14:45
  • woops my bad the rgb parts should not be in the var – Pizza lord - on strike Dec 22 '16 at 14:54
  • 1
    This is the same approach as BoltClock's answer. It also lacks the helpful insight as to why it works. There's no real point in keeping it - I recommend deleting it. – Zach Saucier May 01 '23 at 22:41
  • this is a 7 year old answer, i do not understand why you comment on it after so long. If the other answer is better it will get more upvotes and will be shown first. – Pizza lord - on strike May 02 '23 at 06:17
0

I tried the methods listed above, but sadly none of them have worked. As I am currently using sass(scss syntax) right now I decided to use another method in which instead of putting the variable inside in rgba I placed it in another variable with already decreased opacity.

:root {
 --color-sass: hsla(331, 50%, 60%);
 --color-sass-dimmed: hsla(331, 50%, 60%, 0.8);  
}
brint
  • 1
  • 1
-1

You can set specific variable/value for each color - the original and the one with opacity:

:root {
  --color: #F00;
  --color-opacity: rgba(255, 0, 0, 0.5);
}
#a1 {
  background: var(--color);
} 
#a2 {
  background: var(--color-opacity);
}
<div id="a1">asdf</div>
<div id="a2">asdf</div>

If you can't use this and you are ok with javascript solution, you can use this one:

$(function() {
  $('button').click(function() {
    bgcolor = $('#a2').css('backgroundColor');
    rgb_value = bgcolor.match(/\d+,\s?\d+,\s?\d+/)[0]
    $('#a2').css('backgroundColor', 'rgba(' + rgb_value + ', 0.5)');
  });
});
:root {
  --color: #F00;
}
#a1 {
  background: var(--color);
} 
#a2 {
  background: var(--color);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="a1">asdf</div>
<div id="a2">asdf</div>
<button>Click to change opacity</button>
Dekel
  • 60,707
  • 10
  • 101
  • 129
  • 2
    The opacity value will change, so it would be annoying to create a variable for every opacity. – jotch Oct 13 '16 at 01:00
-1

It seems that I found a solution which works for me.

The following code adds black background color with 60% opacity:

background: color-mix(in lab, transparent 40%, black)

I guess, instead of "black" you could use any CSS variable.

emvaized
  • 884
  • 12
  • 23
-2

:root {
  --color: 00, 125, 250;
}

body {
  color: #fff;
  background-color: #fff;
  font-family: Arial, "Arial", sans-serif;
}

#element {
  background-color: rgba(var(--color), 0.8);
}
<p id="element">If you can see this, your browser supports custom properties.</p>

The reason this works is because the variable color returns the value of 0,125,250 in the rgba color. After rendering, the rgba returns rgba(0,125,250,0.8)

-3

If you love hex colors like me there is another solution. The hex value is 6 digits after that is the alpha value. 00 is 100% transparency 99 is about 75% then it uses the alphabet 'a1-af' then 'b1-bf' ending with 'ff' which is 100% opaque.

:root {
--color: #F00;
}

#element {
background: var(--color)f6;
}
Seth M
  • 37
  • 1
    Unfortunately, I don't think this works. 8 digit hex code support is starting to spread, but it doesn't look like the trick used with the accepted answer works with them. Example: http://jsbin.com/nacuharige/edit?css,output – jotch Aug 25 '18 at 23:43
  • 1
    This does not work, although it would be a great solution if it did. – Braden Rockwell Napier Jun 05 '20 at 03:56
  • As of my posting, this is now available in nearly 94% of currently used browsers [https://caniuse.com/css-rrggbbaa]. I've gone down this route as all my colour variables were already saved as HEX values, so adding the extra 2 characters to indicate alpha was the perfect solution. – Rillus Nov 02 '21 at 14:27
  • 1
    @Rillus, can you provide working example? Seems like this construction is not supported. – Qwertiy Dec 06 '21 at 04:33
  • I now realise my implementation doesn't match up with what this poster used and that CSS variable concatenation i.e. `var(--color)f6` doesn't work (the value produced in this case would be `#f0f0f0 f6`, which is invalid). My successfully working result was using Vue3 and referring to a colour property imported by the js e.g: ` background-color: v-bind(this.colourHex + 'f6');` Apologies for the confusion. – Rillus Dec 08 '21 at 10:08
  • 1
    As of 2022 I'm disappointed that it doesn't work. Why does string concatenation like `var(--color)f6` not work, but `rgba(var(--color), 0.8);` does perfectly? – Samuel Gfeller Apr 13 '22 at 12:23
  • It does not work because css uses tokens and not strings, each token can be considered separated by spaces so `var(--color)f6` is equivalent to `token(var(--color)) token(f6)` which produces a background with 2 tokens resulting in `background: #F00 F6` which is not a valid background rule – Tofandel Jun 14 '23 at 14:08
-3

If Relative color syntax is not supported yet you can try:

Adding Transparency to CSS Colors with JavaScript

If you want to add transparency to a named CSS color like 'red' or any other color format, you can use a simple JavaScript function like the one below.

function addTransparency(color, alpha) {
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.setFillColor(color, alpha);
  return ctx.fillStyle;
}

This function uses the deprecated .setFillColor() method of the Canvas API to set the fill style of a canvas context with the desired color and alpha values. It returns the rgba string value of the fill style, which represents the color with the desired transparency.

You can call this function with a named CSS color and an alpha value between 0 and 1 to get the corresponding rgba string value. For example:

addTransparency('red', .1); \\returns rgba(255, 0, 0, 0.1)

Although .setFillColor() is depreciated it is still implemented in some browsers (blink & webkit). If setFillColor() ever gets dropped completely hopefully we'll have Relative color syntax by then.

Usage with CSS variables

/* Use the getComputedStyle() method to get the current value of the CSS variables --color and --alpha variable. */

const color = getComputedStyle(document.documentElement).getPropertyValue('--color');
const alpha = getComputedStyle(document.documentElement).getPropertyValue('--alpha');

/* Call the addTransparency() function with the color and alpha values as arguments, and store the result in a variable. */

const transparentColor = addTransparency(color, alpha);

/* Use the setProperty() method of the CSSStyleDeclaration interface to set the value of the --color CSS variable to the value of the transparentColor variable. */

document.documentElement.style.setProperty('--color', transparentColor);


function addTransparency(color, alpha) {
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.setFillColor(color, alpha);
  return ctx.fillStyle;
}
:root {
  --color: red;
  --alpha: 0.1;
}
body {
  background-color: var(--color);
}
<h1>Hello World</h1>
  • `I lost everything I typed` Hello and welcome. I suggest you type your answer in a text editor and then paste it in the page to avoid this kind of issues. :) – Eric Aya Dec 29 '22 at 10:43
-4

I found simple workaround, yet I admit not ideal, using filter.

definition:

:root {
  --color: #f0f0f0;
}

override:

#element {
  background: var(--color);
  filter: opacity(0.5);
}
ggrrin
  • 5
  • 4
  • This is like applying `opacity: 0.5;`: it affects the `opacity` of the whole element, and not only the color of the `background`. Feel free to edit your message to make it clear. – meduz' Mar 12 '23 at 17:42
-15

In CSS you should be able to either use rgba values:

#element {
  background: rgba(240, 240, 240, 0.5);
}

or just set the opacity:

#element {
  background: #f0f0f0;
  opacity: 0.5;    
}
Patrick H.
  • 66
  • 11
  • 1
    I am unable to hardcode an rgba value, I am using color variables. I should have mentioned I cannot use opacity because I will have a background image that should not be transparent. – jotch Oct 13 '16 at 00:58
  • 1
    This isn't a solution b/c if you only want the BG to have transparency but the full element to have opacity then adding opacity to everything isn't helpful. – Levidps Feb 06 '20 at 00:03