Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Default ringColor has opacity baked into it
<!-- Please provide all of the information requested below. We're a small team and without all of this information it's not possible for us to help and your bug report will be closed. -->

**What version of Tailwind CSS are you using?**

v3.0.22

**What build tool (or framework if it abstracts the build tool) are you using?**

postcss 8.4.6, webpack 5.65.0

**What version of Node.js are you using?**

For example: v17.5.0

**What browser are you using?**

Edge (Chromium)

**What operating system are you using?**

macOS

**Reproduction URL**

https://play.tailwindcss.com/DXtIUnfIC0

**Describe your issue**

When trying to set the default color for `ringColor`, it seems like an opacity of `0.5` is being applied regardless of the defined color. In my example I wanted to set the default ring color to be `#000`, but the output ends up becoming `--tw-ring-color: rgb(0 0 0 / 0.5);`.

Ideally, I'd like to be able override that default opacity with a defined opacity of my own choosing, or have the color defined for `DEFAULT` just override whatever opacity is being applied.


Hints:
Hey! This is because `ringOpacity` has a `DEFAULT` of `0.5`. If you override that as well you get the result you want:

https://play.tailwindcss.com/CwV0YGg3LS?file=config

The whole `ringOpacity` thing isn’t really documented anymore though because we are encouraging the opacity modifier as of v3, so going to leave this open just to prompt us to figure out how to document this or what to do about it.
Ah, fantastic. Thank you.
> encouraging the opacity modifier

I see that intellisense in play.tailwind.com has class suggestions for `<div class="ring/` but they don't have associated styles. Are there plans to support the opacity modifier on the default, maybe `ring/100` or `ring ring/100`? Or is that a separate ring feature request and a separate intellisense bug?
Just revisiting this, spent like 45 minutes talking through it with @thecrypticace and I think we _would_ like to change this so that the `ringOpacity.DEFAULT` value didn't inject itself into your custom `ringColor.DEFAULT` value but it would be a breaking change, since anyone who has currently customized `ringColor.DEFAULT` would now see a different color after updating. It feels a little _too_ breaking to just call it a bug fix to me unfortunately.

I think what we're going to do is feature flag this fix so that anyone who runs into this problem and finds this issue can see that there's a flag to opt-in to the breaking change early, and then in v4 we can make that the default and mention it in the upgrade guide.

Not sure when we will tackle this but leaving this as a note for myself anyways for when we look at this issue again 👍🏻 

In the mean time, changing your `ringOpacity.DEFAULT` as explained earlier is the right way to solve this.

Patch:
diff --git a/src/corePlugins.js b/src/corePlugins.js
--- a/src/corePlugins.js
+++ b/src/corePlugins.js
@@ -1977,13 +1977,20 @@ export let corePlugins = {
     )
   },
 
-  ringWidth: ({ matchUtilities, addDefaults, addUtilities, theme }) => {
-    let ringOpacityDefault = theme('ringOpacity.DEFAULT', '0.5')
-    let ringColorDefault = withAlphaValue(
-      theme('ringColor')?.DEFAULT,
-      ringOpacityDefault,
-      `rgb(147 197 253 / ${ringOpacityDefault})`
-    )
+  ringWidth: ({ matchUtilities, addDefaults, addUtilities, theme, config }) => {
+    let ringColorDefault = (() => {
+      if (flagEnabled(config(), 'respectDefaultRingColorOpacity')) {
+        return theme('ringColor.DEFAULT')
+      }
+
+      let ringOpacityDefault = theme('ringOpacity.DEFAULT', '0.5')
+
+      return withAlphaValue(
+        theme('ringColor')?.DEFAULT,
+        ringOpacityDefault,
+        `rgb(147 197 253 / ${ringOpacityDefault})`
+      )
+    })()
 
     addDefaults('ring-width', {
       '--tw-ring-inset': ' ',
diff --git a/src/featureFlags.js b/src/featureFlags.js
--- a/src/featureFlags.js
+++ b/src/featureFlags.js
@@ -6,7 +6,7 @@ let defaults = {
 }
 
 let featureFlags = {
-  future: ['hoverOnlyWhenSupported'],
+  future: ['hoverOnlyWhenSupported', 'respectDefaultRingColorOpacity'],
   experimental: ['optimizeUniversalDefaults', 'variantGrouping'],
 }
 
diff --git a/src/util/getAllConfigs.js b/src/util/getAllConfigs.js
--- a/src/util/getAllConfigs.js
+++ b/src/util/getAllConfigs.js
@@ -9,6 +9,13 @@ export default function getAllConfigs(config) {
 
   const features = {
     // Add experimental configs here...
+    respectDefaultRingColorOpacity: {
+      theme: {
+        ringColor: {
+          DEFAULT: '#3b82f67f',
+        },
+      },
+    },
   }
 
   const experimentals = Object.keys(features)
diff --git a/stubs/defaultConfig.stub.js b/stubs/defaultConfig.stub.js
--- a/stubs/defaultConfig.stub.js
+++ b/stubs/defaultConfig.stub.js
@@ -720,7 +720,7 @@ module.exports = {
       8: '8px',
     },
     ringColor: ({ theme }) => ({
-      DEFAULT: theme('colors.blue.500', '#3b82f6'),
+      DEFAULT: theme(`colors.blue.500`, '#3b82f6'),
       ...theme('colors'),
     }),
     ringOffsetColor: ({ theme }) => theme('colors'),


Test patch:
diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js
--- a/tests/basic-usage.test.js
+++ b/tests/basic-usage.test.js
@@ -430,3 +430,237 @@ it('supports multiple backgrounds as arbitrary values even if only some are quot
     `)
   })
 })
+
+it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
+  let config = {
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.5)' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
+  let config = {
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringOpacity: {
+        DEFAULT: 0.75,
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: 'rgb(59 130 246 / 0.75)' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
+  let config = {
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringColor: {
+        DEFAULT: '#ff7f7f',
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
+  let config = {
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringColor: {
+        DEFAULT: '#ff7f7f00',
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: 'rgb(255 127 127 / 0.5)' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
+  let config = {
+    future: { respectDefaultRingColorOpacity: true },
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: '#3b82f67f' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
+  let config = {
+    future: { respectDefaultRingColorOpacity: true },
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringOpacity: {
+        DEFAULT: 0.75,
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: '#3b82f67f' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
+  let config = {
+    future: { respectDefaultRingColorOpacity: true },
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringColor: {
+        DEFAULT: '#ff7f7f',
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: '#ff7f7f' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})
+
+it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
+  let config = {
+    future: { respectDefaultRingColorOpacity: true },
+    content: [{ raw: html`<div class="ring"></div>` }],
+    corePlugins: { preflight: false },
+    theme: {
+      ringColor: {
+        DEFAULT: '#ff7f7f00',
+      },
+    },
+  }
+
+  let input = css`
+    @tailwind base;
+    @tailwind utilities;
+  `
+
+  return run(input, config).then((result) => {
+    expect(result.css).toMatchFormattedCss(css`
+      ${defaults({ defaultRingColor: '#ff7f7f00' })}
+      .ring {
+        --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width)
+          var(--tw-ring-offset-color);
+        --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width))
+          var(--tw-ring-color);
+        box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+      }
+    `)
+  })
+})