Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Add support for cssvar colors to `withAlphaVariable` and `withAlphaValue`
As mentioned in this discussion https://github.com/tailwindlabs/tailwindcss/discussions/4205 (and can be seen in this demo https://play.tailwindcss.com/Q7aU4F9lpb) currently using css variables to define colors breaks down somewhat if the are used anywhere were an alpha channel is added.

I've extended the `withAlphaVariable` and `withAlphaValue` utils to check if a css variable was defined as numeric values `--color-var: 255, 0, 255` and consequently used like this in the config `DEFAULT: 'rgb(var(--color-var))'`.

My implementation will work for both `rgb` and `hsl` as long as they were valid colors to begin with and should be somewhat resistant to inconsistent whitespace use.

The only downside of this implementation is that you won't be able to use variables containing hex colors directly but you need to define them as a numeric triple as shown in my example above.

Since this is my first PR to this project I really hope everything is in order but feel free discuss issues of this solution with me 🙂
Suggestion: improve support for color css variables
### What version of @tailwindcss/jit are you using?

0.1.6

### What version of Node.js are you using?

14

### What browser are you using?

all

### What operating system are you using?

linux

### Reproduction repository

no need

Today with tailwind, it is not that easy to use the colors stored in a css variable.
If we do this `--color-primary: theme(colors.blue.500);`,  `--color-primary: #hexcolor;`,   
 `--color-primary: rgb (255,255,255)` or other color format,  we definitely lose the ability to add color opacity.  

So, we are forced to store the color in an unconventional way: `--color-primary: 59, 130, 246;`.
This brings some disappointment: hand conversion, no visual on colors in IDEs  

![Imgur](https://i.imgur.com/Ow4tDFX.jpg)

I think it will be interesting to have a function available that allows at build time to convert the most common color format to one that takes advantage of opacity. Like that  

![Imgur](https://i.imgur.com/MT3T89b.jpg)
```css
/* result to */
--color-primary: 59, 130, 246;
```
- After that, in the same way, we can declare the color in tailwind.config

```js
// ...
colors: {
                yellow: colors.orange,
                primary: varToRgba('--color-primary'),
}
// ...
```

- And / or, as in a recent similar library, proposing to create classes on the fly with a syntax like this: `border-$color-primary` (with opacity support of course)


Thanks a lot for your work! 



Hints:
It took me a bit to figure out how to get a working local build with a custom config, but I was also able to verify that my demo is now working correctly. (The second square uses a non-rgb variable and therefore it's expected that it isn't semi-transparent)

![image](https://user-images.githubusercontent.com/3532342/122193527-da6d2600-ce94-11eb-9572-6b17e677e914.png)

Any plans to merge this? We really need a solution for custom css variables with bg-opacity.
@memic84 it's pretty hidden in the documentation but you can actually use this as a "workaround":

```JS
const getColor = (colorVar, { opacityVariable, opacityValue }) => {
  if (opacityValue !== undefined) {
    return `rgba(var(${colorVar}), ${opacityValue})`;
  }
  if (opacityVariable !== undefined) {
    return `rgba(var(${colorVar}), var(${opacityVariable}, 1))`;
  }

  return `rgb(var(${colorVar}))`;
};

module.exports = {
  // ...
  theme: {
    extend: {
      colors: {
        primary: (params) => getColor('--color-primary-rgb', params),
      }
   }
}
```

I still would like this PR merged, because I think everyone would benefit from making this easier.
> @memic84 it's pretty hidden in the documentation but you can actually use this as a "workaround":
> 
> ```js
> const getColor = (colorVar, { opacityVariable, opacityValue }) => {
>   if (opacityValue !== undefined) {
>     return `rgba(var(${colorVar}), ${opacityValue})`;
>   }
>   if (opacityVariable !== undefined) {
>     return `rgba(var(${colorVar}), var(${opacityVariable}, 1))`;
>   }
> 
>   return `rgb(var(${colorVar}))`;
> };
> 
> module.exports = {
>   // ...
>   theme: {
>     extend: {
>       colors: {
>         primary: (params) => getColor('--color-primary-rgb', params),
>       }
>    }
> }
> ```
> 
> I still would like this PR merged, because I think everyone would benefit from making this easier.

This doesn't seem to work in combination with `@apply`.
@PeeJeeDR works for me without a problem in my angular project, even VS Code intellisense picks it up with the tailwind extension.
Hey thanks for this! Any thoughts on how this could possibly work with both the old and new rgb/hsl formats?

```
--color-1: 12, 34, 56
rgb(var(--color-1))

--color-2: 12 34 56
rgb(var(--color-2)
```

Both of those are valid, and depending on the syntax used, the comma before the alpha channel needs to be omitted 🤔 
@adamwathan good catch, I think there is no good way of handling this besides convention or maybe a config option which way you intend to use. Using JIT you could try to read the value of the var, but I don't know if the overhead is worth it.
We're going to make some internal changes to use the new rgb/hsl format for all colors we generate internally, then let's update this to use that as well and just assume people are using space-separated syntax. Will target v3 which will be in alpha/beta in like a month, and released in November 👍🏻 
Hey we've updated Tailwind internally to use the space separated syntax for all color related work that we do. Do you mind updating this PR to work based on that assumption? 🙏
@reinink sure I will try to look into it over the weekend 😉
@reinink @adamwathan I've just started looking into this and I was wondering if it's ok to be very specific with the expected format.

```
// this is going to work
rgb(var(--my-color-var)) // --my-color-var: 255 0 0;
hsl(var(--my-color-var)) // --my-color-var: 0 100% 50%;

// this is not going to work
rgb(var(--my-color-var)) // --my-color-var: 255, 0, 0;
hsl(var(--my-color-var)) // --my-color-var: 0, 100%, 50%;

rgb(var(--my-color-var)) // --my-color-var: 255 0 0 / 0.1;
hsl(var(--my-color-var)) // --my-color-var: 0 100% 50% / 0.1;

rgba(var(--my-color-var)) // --my-color-var: 255 0 0 0.1;
hsla(var(--my-color-var)) // --my-color-var: 0 100% 50% 0.1;
```

What do you think? Supporting the `rgba` syntax would in theory be possible, but then the set opacity level (e.g. bg-opacity-10) can still not be applied.
You can take a look if you are happy with my current changes. rgba/hsla values stay as is, but for rgb/hsl values not in the described format this could be a breaking change if anyone is currently using those as color values.
Just following up on this (we're doing a big GitHub clean up) — my only concern is this one not working:

```css
:root {
  --my-color-var: 255 0 0 / 0.1;
}
```

It feels bad that if someone wants to define colors that have the opacity baked in that everything would break.

Maybe this is okay though because if someone wanted to do that they wouldn't define the color as channels but rather as a full color?

```css
:root {
  --my-color-var: rgb(255 0 0 / 0.1);
}
```

I kinda wonder though if trying to do this all automagically is just too much and instead we could just offer a helper function people could use? For example:

```js
// tailwind.config.js
module.exports = {
  theme: {
    colors: ({ rgb, hsl }) => ({
      primary: rgb('--color-primary'),
      secondary: hsl('--color-secondary'),
      // ...
    })
  }
}
```

You'd need to know about those helpers of course but it feels a lot more mechanically simple to me, with less chances of discovering an edge case down the road that we've accidentally made impossible to solve without a breaking change.
Would love to see variables get first class support
Currently using https://github.com/mertasan/tailwindcss-variables which is great for generating and using variables, but overriding means writing css like `colors-primary-rgb: 255, 0, 255;`
If you're revisiting the color system, please make it work with CSS Color Level 4 (see: #2218) as well.

The way the colors are deconstructed internally makes it absolutely impossible to specify any color using the CSS color() function and have it work with opacity.

Patch:
diff --git a/src/corePlugins.js b/src/corePlugins.js
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -1964,10 +1964,16 @@ export let corePlugins = {
     })
   },
 
-  ringColor: ({ matchUtilities, theme }) => {
+  ringColor: ({ matchUtilities, theme, corePlugins }) => {
     matchUtilities(
       {
         ring: (value) => {
+          if (!corePlugins('ringOpacity')) {
+            return {
+              '--tw-ring-color': toColorValue(value),
+            }
+          }
+
           return withAlphaVariable({
             color: value,
             property: '--tw-ring-color',
diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js
--- a/src/util/resolveConfig.js
+++ b/src/util/resolveConfig.js
@@ -66,6 +66,36 @@ const configUtils = {
         {}
       )
   },
+  rgb(property) {
+    if (!property.startsWith('--')) {
+      throw new Error(
+        'The rgb() helper requires a custom property name to be passed as the first argument.'
+      )
+    }
+
+    return ({ opacityValue }) => {
+      if (opacityValue === undefined || opacityValue === 1) {
+        return `rgb(var(${property}) / 1.0)`
+      }
+
+      return `rgb(var(${property}) / ${opacityValue})`
+    }
+  },
+  hsl(property) {
+    if (!property.startsWith('--')) {
+      throw new Error(
+        'The hsl() helper requires a custom property name to be passed as the first argument.'
+      )
+    }
+
+    return ({ opacityValue }) => {
+      if (opacityValue === undefined || opacityValue === 1) {
+        return `hsl(var(${property}) / 1)`
+      }
+
+      return `hsl(var(${property}) / ${opacityValue})`
+    }
+  },
 }
 
 function value(valueToResolve, ...args) {


Test patch:
diff --git a/tests/opacity.test.js b/tests/opacity.test.js
--- a/tests/opacity.test.js
+++ b/tests/opacity.test.js
@@ -95,3 +95,335 @@ test('colors defined as functions work when opacity plugins are disabled', () =>
     `)
   })
 })
+
+it('can use rgb helper when defining custom properties for colors (opacity plugins enabled)', () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="divide-primary"></div>
+          <div class="divide-primary divide-opacity-50"></div>
+          <div class="border-primary"></div>
+          <div class="border-primary border-opacity-50"></div>
+          <div class="bg-primary"></div>
+          <div class="bg-primary bg-opacity-50"></div>
+          <div class="text-primary"></div>
+          <div class="text-primary text-opacity-50"></div>
+          <div class="placeholder-primary"></div>
+          <div class="placeholder-primary placeholder-opacity-50"></div>
+          <div class="ring-primary"></div>
+          <div class="ring-primary ring-opacity-50"></div>
+        `,
+      },
+    ],
+    theme: {
+      colors: ({ rgb }) => ({
+        primary: rgb('--color-primary'),
+      }),
+    },
+  }
+
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-primary > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-opacity: 1;
+        border-color: rgb(var(--color-primary) / var(--tw-divide-opacity));
+      }
+      .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-opacity: 0.5;
+      }
+      .border-primary {
+        --tw-border-opacity: 1;
+        border-color: rgb(var(--color-primary) / var(--tw-border-opacity));
+      }
+      .border-opacity-50 {
+        --tw-border-opacity: 0.5;
+      }
+      .bg-primary {
+        --tw-bg-opacity: 1;
+        background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
+      }
+      .bg-opacity-50 {
+        --tw-bg-opacity: 0.5;
+      }
+      .text-primary {
+        --tw-text-opacity: 1;
+        color: rgb(var(--color-primary) / var(--tw-text-opacity));
+      }
+      .text-opacity-50 {
+        --tw-text-opacity: 0.5;
+      }
+      .placeholder-primary::placeholder {
+        --tw-placeholder-opacity: 1;
+        color: rgb(var(--color-primary) / var(--tw-placeholder-opacity));
+      }
+      .placeholder-opacity-50::placeholder {
+        --tw-placeholder-opacity: 0.5;
+      }
+      .ring-primary {
+        --tw-ring-opacity: 1;
+        --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity));
+      }
+      .ring-opacity-50 {
+        --tw-ring-opacity: 0.5;
+      }
+    `)
+  })
+})
+
+it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="divide-primary"></div>
+          <div class="divide-primary/50"></div>
+          <div class="border-primary"></div>
+          <div class="border-primary/50"></div>
+          <div class="bg-primary"></div>
+          <div class="bg-primary/50"></div>
+          <div class="text-primary"></div>
+          <div class="text-primary/50"></div>
+          <div class="placeholder-primary"></div>
+          <div class="placeholder-primary/50"></div>
+          <div class="ring-primary"></div>
+          <div class="ring-primary/50"></div>
+        `,
+      },
+    ],
+    theme: {
+      colors: ({ rgb }) => ({
+        primary: rgb('--color-primary'),
+      }),
+    },
+    corePlugins: {
+      backgroundOpacity: false,
+      borderOpacity: false,
+      divideOpacity: false,
+      placeholderOpacity: false,
+      textOpacity: false,
+      ringOpacity: false,
+    },
+  }
+
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      .divide-primary > :not([hidden]) ~ :not([hidden]) {
+        border-color: rgb(var(--color-primary) / 1);
+      }
+      .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+        border-color: rgb(var(--color-primary) / 0.5);
+      }
+      .border-primary {
+        border-color: rgb(var(--color-primary) / 1);
+      }
+      .border-primary\/50 {
+        border-color: rgb(var(--color-primary) / 0.5);
+      }
+      .bg-primary {
+        background-color: rgb(var(--color-primary) / 1);
+      }
+      .bg-primary\/50 {
+        background-color: rgb(var(--color-primary) / 0.5);
+      }
+      .text-primary {
+        color: rgb(var(--color-primary) / 1);
+      }
+      .text-primary\/50 {
+        color: rgb(var(--color-primary) / 0.5);
+      }
+      .placeholder-primary::placeholder {
+        color: rgb(var(--color-primary) / 1);
+      }
+      .placeholder-primary\/50::placeholder {
+        color: rgb(var(--color-primary) / 0.5);
+      }
+      .ring-primary {
+        --tw-ring-color: rgb(var(--color-primary) / 1);
+      }
+      .ring-primary\/50 {
+        --tw-ring-color: rgb(var(--color-primary) / 0.5);
+      }
+    `)
+  })
+})
+
+it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="divide-primary"></div>
+          <div class="divide-primary divide-opacity-50"></div>
+          <div class="border-primary"></div>
+          <div class="border-primary border-opacity-50"></div>
+          <div class="bg-primary"></div>
+          <div class="bg-primary bg-opacity-50"></div>
+          <div class="text-primary"></div>
+          <div class="text-primary text-opacity-50"></div>
+          <div class="placeholder-primary"></div>
+          <div class="placeholder-primary placeholder-opacity-50"></div>
+          <div class="ring-primary"></div>
+          <div class="ring-primary ring-opacity-50"></div>
+        `,
+      },
+    ],
+    theme: {
+      colors: ({ hsl }) => ({
+        primary: hsl('--color-primary'),
+      }),
+    },
+  }
+
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-primary > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-opacity: 1;
+        border-color: hsl(var(--color-primary) / var(--tw-divide-opacity));
+      }
+      .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+        --tw-divide-opacity: 0.5;
+      }
+      .border-primary {
+        --tw-border-opacity: 1;
+        border-color: hsl(var(--color-primary) / var(--tw-border-opacity));
+      }
+      .border-opacity-50 {
+        --tw-border-opacity: 0.5;
+      }
+      .bg-primary {
+        --tw-bg-opacity: 1;
+        background-color: hsl(var(--color-primary) / var(--tw-bg-opacity));
+      }
+      .bg-opacity-50 {
+        --tw-bg-opacity: 0.5;
+      }
+      .text-primary {
+        --tw-text-opacity: 1;
+        color: hsl(var(--color-primary) / var(--tw-text-opacity));
+      }
+      .text-opacity-50 {
+        --tw-text-opacity: 0.5;
+      }
+      .placeholder-primary::placeholder {
+        --tw-placeholder-opacity: 1;
+        color: hsl(var(--color-primary) / var(--tw-placeholder-opacity));
+      }
+      .placeholder-opacity-50::placeholder {
+        --tw-placeholder-opacity: 0.5;
+      }
+      .ring-primary {
+        --tw-ring-opacity: 1;
+        --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity));
+      }
+      .ring-opacity-50 {
+        --tw-ring-opacity: 0.5;
+      }
+    `)
+  })
+})
+
+it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => {
+  let config = {
+    content: [
+      {
+        raw: html`
+          <div class="divide-primary"></div>
+          <div class="divide-primary/50"></div>
+          <div class="border-primary"></div>
+          <div class="border-primary/50"></div>
+          <div class="bg-primary"></div>
+          <div class="bg-primary/50"></div>
+          <div class="text-primary"></div>
+          <div class="text-primary/50"></div>
+          <div class="placeholder-primary"></div>
+          <div class="placeholder-primary/50"></div>
+          <div class="ring-primary"></div>
+          <div class="ring-primary/50"></div>
+        `,
+      },
+    ],
+    theme: {
+      colors: ({ hsl }) => ({
+        primary: hsl('--color-primary'),
+      }),
+    },
+    corePlugins: {
+      backgroundOpacity: false,
+      borderOpacity: false,
+      divideOpacity: false,
+      placeholderOpacity: false,
+      textOpacity: false,
+      ringOpacity: false,
+    },
+  }
+
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-primary > :not([hidden]) ~ :not([hidden]) {
+        border-color: hsl(var(--color-primary) / 1);
+      }
+      .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+        border-color: hsl(var(--color-primary) / 0.5);
+      }
+      .border-primary {
+        border-color: hsl(var(--color-primary) / 1);
+      }
+      .border-primary\/50 {
+        border-color: hsl(var(--color-primary) / 0.5);
+      }
+      .bg-primary {
+        background-color: hsl(var(--color-primary) / 1);
+      }
+      .bg-primary\/50 {
+        background-color: hsl(var(--color-primary) / 0.5);
+      }
+      .text-primary {
+        color: hsl(var(--color-primary) / 1);
+      }
+      .text-primary\/50 {
+        color: hsl(var(--color-primary) / 0.5);
+      }
+      .placeholder-primary::placeholder {
+        color: hsl(var(--color-primary) / 1);
+      }
+      .placeholder-primary\/50::placeholder {
+        color: hsl(var(--color-primary) / 0.5);
+      }
+      .ring-primary {
+        --tw-ring-color: hsl(var(--color-primary) / 1);
+      }
+      .ring-primary\/50 {
+        --tw-ring-color: hsl(var(--color-primary) / 0.5);
+      }
+    `)
+  })
+})
+
+it('the rgb helper throws when not passing custom properties', () => {
+  let config = {
+    theme: {
+      colors: ({ rgb }) => ({
+        primary: rgb('anything else'),
+      }),
+    },
+  }
+
+  return expect(run('@tailwind utilities', config)).rejects.toThrow(
+    'The rgb() helper requires a custom property name to be passed as the first argument.'
+  )
+})
+
+it('the hsl helper throws when not passing custom properties', () => {
+  let config = {
+    theme: {
+      colors: ({ hsl }) => ({
+        primary: hsl('anything else'),
+      }),
+    },
+  }
+
+  return expect(run('@tailwind utilities', config)).rejects.toThrow(
+    'The hsl() helper requires a custom property name to be passed as the first argument.'
+  )
+})