Repository: tailwindlabs/tailwindcss

Problem statement:
Disabling an opacity plugin breaks support for custom property colors
### What version of Tailwind CSS are you using?

2.2.7

### What build tool (or framework if it abstracts the build tool) are you using?

Tailwind Play

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

n/a

### What browser are you using?

n/a

### What operating system are you using?

n/a

### Reproduction repository

https://play.tailwindcss.com/YVjDQE13bj

### Describe your issue

Colors defined by a function as described in https://github.com/adamwathan/tailwind-css-variable-text-opacity-demo break for plugins where the associated opacity plugin is disabled.

This is caused by the plugins not calling `withAlphaVariable()` when opacity plugins are disabled:
https://github.com/tailwindlabs/tailwindcss/blob/691ed02f6352da17048dd14f742f7c82919e1455/src/plugins/backgroundColor.js#L11. Since the function 

This behaviour makes it impossible to selectively disable opacity plugins when using colors defined via functions.

Since the example repo shows how the function is expected to support the case where neither `opacityVariable` nor `opacityValue` are provided, I would expect it to work.


Hints:
A workaround is to provide an empty theme instead of disabling the opacity plugin, for example `backgroundOpacity: {}`.

Patch:
diff --git a/src/corePlugins.js b/src/corePlugins.js
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -1014,7 +1014,11 @@ export let divideColor = ({ matchUtilities, theme, corePlugins }) => {
     {
       divide: (value) => {
         if (!corePlugins('divideOpacity')) {
-          return { ['& > :not([hidden]) ~ :not([hidden])']: { 'border-color': value } }
+          return {
+            ['& > :not([hidden]) ~ :not([hidden])']: {
+              'border-color': toColorValue(value),
+            },
+          }
         }
 
         return {
@@ -1174,9 +1178,10 @@ export let borderStyle = ({ addUtilities }) => {
 
 export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
   if (!corePlugins('borderOpacity')) {
+    let value = theme('borderColor.DEFAULT', 'currentColor')
     addBase({
       '@defaults border-width': {
-        'border-color': theme('borderColor.DEFAULT', 'currentColor'),
+        'border-color': toColorValue(value),
       },
     })
   } else {
@@ -1193,7 +1198,9 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
     {
       border: (value) => {
         if (!corePlugins('borderOpacity')) {
-          return { 'border-color': value }
+          return {
+            'border-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1213,7 +1220,9 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
     {
       'border-t': (value) => {
         if (!corePlugins('borderOpacity')) {
-          return { 'border-top-color': value }
+          return {
+            'border-top-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1224,7 +1233,9 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
       },
       'border-r': (value) => {
         if (!corePlugins('borderOpacity')) {
-          return { 'border-right-color': value }
+          return {
+            'border-right-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1235,7 +1246,9 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
       },
       'border-b': (value) => {
         if (!corePlugins('borderOpacity')) {
-          return { 'border-bottom-color': value }
+          return {
+            'border-bottom-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1246,7 +1259,9 @@ export let borderColor = ({ addBase, matchUtilities, theme, corePlugins }) => {
       },
       'border-l': (value) => {
         if (!corePlugins('borderOpacity')) {
-          return { 'border-left-color': value }
+          return {
+            'border-left-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1272,7 +1287,9 @@ export let backgroundColor = ({ matchUtilities, theme, corePlugins }) => {
     {
       bg: (value) => {
         if (!corePlugins('backgroundOpacity')) {
-          return { 'background-color': value }
+          return {
+            'background-color': toColorValue(value),
+          }
         }
 
         return withAlphaVariable({
@@ -1539,7 +1556,7 @@ export let textColor = ({ matchUtilities, theme, corePlugins }) => {
     {
       text: (value) => {
         if (!corePlugins('textOpacity')) {
-          return { color: value }
+          return { color: toColorValue(value) }
         }
 
         return withAlphaVariable({
@@ -1583,7 +1600,11 @@ export let placeholderColor = ({ matchUtilities, theme, corePlugins }) => {
     {
       placeholder: (value) => {
         if (!corePlugins('placeholderOpacity')) {
-          return { '&::placeholder': { color: value } }
+          return {
+            '&::placeholder': {
+              color: toColorValue(value),
+            },
+          }
         }
 
         return {


Test patch:
diff --git a/tests/opacity.test.css b/tests/opacity.test.css
deleted file mode 100644
--- a/tests/opacity.test.css
+++ /dev/null
@@ -1,15 +0,0 @@
-.divide-black > :not([hidden]) ~ :not([hidden]) {
-  border-color: #000;
-}
-.border-black {
-  border-color: #000;
-}
-.bg-black {
-  background-color: #000;
-}
-.text-black {
-  color: #000;
-}
-.placeholder-black::placeholder {
-  color: #000;
-}
diff --git a/tests/opacity.test.html b/tests/opacity.test.html
deleted file mode 100644
--- a/tests/opacity.test.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Title</title>
-    <link rel="stylesheet" href="./tailwind.css" />
-  </head>
-  <body>
-    <div class="divide-black"></div>
-    <div class="border-black"></div>
-    <div class="bg-black"></div>
-    <div class="text-black"></div>
-    <div class="placeholder-black"></div>
-  </body>
-</html>
diff --git a/tests/opacity.test.js b/tests/opacity.test.js
--- a/tests/opacity.test.js
+++ b/tests/opacity.test.js
@@ -1,12 +1,19 @@
-import fs from 'fs'
-import path from 'path'
-
-import { run } from './util/run'
+import { run, html, css } from './util/run'
 
 test('opacity', () => {
   let config = {
     darkMode: 'class',
-    content: [path.resolve(__dirname, './opacity.test.html')],
+    content: [
+      {
+        raw: html`
+          <div class="divide-black"></div>
+          <div class="border-black"></div>
+          <div class="bg-black"></div>
+          <div class="text-black"></div>
+          <div class="placeholder-black"></div>
+        `,
+      },
+    ],
     corePlugins: {
       backgroundOpacity: false,
       borderOpacity: false,
@@ -17,9 +24,74 @@ test('opacity', () => {
   }
 
   return run('@tailwind utilities', config).then((result) => {
-    let expectedPath = path.resolve(__dirname, './opacity.test.css')
-    let expected = fs.readFileSync(expectedPath, 'utf8')
+    expect(result.css).toMatchCss(css`
+      .divide-black > :not([hidden]) ~ :not([hidden]) {
+        border-color: #000;
+      }
+      .border-black {
+        border-color: #000;
+      }
+      .bg-black {
+        background-color: #000;
+      }
+      .text-black {
+        color: #000;
+      }
+      .placeholder-black::placeholder {
+        color: #000;
+      }
+    `)
+  })
+})
+
+test('colors defined as functions work when opacity plugins are disabled', () => {
+  let config = {
+    darkMode: 'class',
+    content: [
+      {
+        raw: html`
+          <div class="divide-primary"></div>
+          <div class="border-primary"></div>
+          <div class="bg-primary"></div>
+          <div class="text-primary"></div>
+          <div class="placeholder-primary"></div>
+        `,
+      },
+    ],
+    theme: {
+      colors: {
+        primary: ({ opacityValue }) =>
+          opacityValue === undefined
+            ? 'rgb(var(--color-primary))'
+            : `rgb(var(--color-primary) / ${opacityValue})`,
+      },
+    },
+    corePlugins: {
+      backgroundOpacity: false,
+      borderOpacity: false,
+      divideOpacity: false,
+      placeholderOpacity: false,
+      textOpacity: false,
+    },
+  }
 
-    expect(result.css).toMatchCss(expected)
+  return run('@tailwind utilities', config).then((result) => {
+    expect(result.css).toMatchCss(css`
+      .divide-primary > :not([hidden]) ~ :not([hidden]) {
+        border-color: rgb(var(--color-primary));
+      }
+      .border-primary {
+        border-color: rgb(var(--color-primary));
+      }
+      .bg-primary {
+        background-color: rgb(var(--color-primary));
+      }
+      .text-primary {
+        color: rgb(var(--color-primary));
+      }
+      .placeholder-primary::placeholder {
+        color: rgb(var(--color-primary));
+      }
+    `)
   })
 })