Repository: tailwindlabs/tailwindcss (TypeScript)

Issue:
Some arbitrary pseudo-elements are moved inside `:is()` when using `important` config option, breaking the selector
<blockquote><details><summary><b>Tailwind CSS/browser/device version information</b></summary></br >

**What version of Tailwind CSS are you using?**

v3.3.2

**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?**

Chrome v114.0.5735.90

**What operating system are you using?**

macOS v13.1 (22C65)

</details></blockquote>

**Reproduction URL**

* ✅ Works when not using `important` (w/o `:is()`): https://play.tailwindcss.com/yOqpv2N25J
* ❌ Breaks when using `important` (w/ `:is()`): https://play.tailwindcss.com/gVUTg4DLNK

**Describe your issue**

This code:

**Config**

```js
module.exports = { important: 'body' }
```

**Markup**

```html
<progress value="5" max="10" class="
  bg-gray-300
  [&::-webkit-progress-bar]:bg-gray-300
  [&::-moz-progress-bar]:bg-blue-500
  [&::-webkit-progress-value]:bg-blue-500"
/>
```

**Generated CSS** (invalid)

```css
body :is(.\[\&\:\:-webkit-progress-value\]\:bg-blue-500::-webkit-progress-value) {
  --tw-bg-opacity: 1;
  background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
```

Instead of including the pseudo-element on the inside of `:is()`, it should remain on the outside, where it is valid.

For example, the rendered output of `[&::-moz-progress-bar]:[&:hover]:font-bold` should be…

```css
body :is(.\[\&\:\:-moz-progress-bar\]\:\[\&\:hover\]\:font-bold:hover)::-moz-progress-bar {
  font-weight: 700;
}
```

but is currently…

```css
body :is(.\[\&\:\:-moz-progress-bar\]\:\[\&\:hover\]\:font-bold:hover::-moz-progress-bar) {
  font-weight: 700;
}
```

This is unique to pseudo-elements and does not apply to pseudo-classes. Pseudo-classes should remain on the inside of `:is()`. This already works for most pseudo-elements in TailwindCSS, so maybe it's just keeping an index of all of them. Could TailwindCSS be set up such that any time a double `::` appears, it instructs the compiler to display it after the `:is()`?

I considered maybe it's the case that it keeps them all inside unless on a specific list since some devs write `:before` vs. `::before`, and trying to determine that intelligently for some like that is fine, but could the double `::`—when used—be set to always indicate a pseudo-element (vs. a pseudo-class)?


Hints:
I meet the same problem..Even using the @csstools/postcss-is-pseudo-class plugin can't convert successfully invalid:is()

Patch:
diff --git a/src/util/pseudoElements.js b/src/util/pseudoElements.js
--- a/src/util/pseudoElements.js
+++ b/src/util/pseudoElements.js
@@ -19,12 +19,13 @@
 // **Jumpable**
 // Any terminal element may "jump" over combinators when moving to the end of the selector
 //
-// This is a backwards-compat quirk of :before and :after variants.
+// This is a backwards-compat quirk of pseudo element variants from earlier versions of Tailwind CSS.
 
 /** @typedef {'terminal' | 'actionable' | 'jumpable'} PseudoProperty */
 
 /** @type {Record<string, PseudoProperty[]>} */
 let elementProperties = {
+  // Pseudo elements from the spec
   '::after': ['terminal', 'jumpable'],
   '::backdrop': ['terminal', 'jumpable'],
   '::before': ['terminal', 'jumpable'],
@@ -41,18 +42,14 @@ let elementProperties = {
   '::spelling-error': ['terminal'],
   '::target-text': ['terminal'],
 
-  // other
+  // Pseudo elements from the spec with special rules
   '::file-selector-button': ['terminal', 'actionable'],
-  '::-webkit-progress-bar': ['terminal', 'actionable'],
 
-  // Webkit scroll bar pseudo elements can be combined with user-action pseudo classes
-  '::-webkit-scrollbar': ['terminal', 'actionable'],
-  '::-webkit-scrollbar-button': ['terminal', 'actionable'],
-  '::-webkit-scrollbar-thumb': ['terminal', 'actionable'],
-  '::-webkit-scrollbar-track': ['terminal', 'actionable'],
-  '::-webkit-scrollbar-track-piece': ['terminal', 'actionable'],
-  '::-webkit-scrollbar-corner': ['terminal', 'actionable'],
-  '::-webkit-resizer': ['terminal', 'actionable'],
+  // Library-specific pseudo elements used by component libraries
+  // These are Shadow DOM-like
+  '::deep': ['actionable'],
+  '::v-deep': ['actionable'],
+  '::ng-deep': ['actionable'],
 
   // Note: As a rule, double colons (::) should be used instead of a single colon
   // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
@@ -65,8 +62,8 @@ let elementProperties = {
 
   // The default value is used when the pseudo-element is not recognized
   // Because it's not recognized, we don't know if it's terminal or not
-  // So we assume it can't be moved AND can have user-action pseudo classes attached to it
-  __default__: ['actionable'],
+  // So we assume it can be moved AND can have user-action pseudo classes attached to it
+  __default__: ['terminal', 'actionable'],
 }
 
 /**


Test patch:
diff --git a/tests/util/apply-important-selector.test.js b/tests/util/apply-important-selector.test.js
--- a/tests/util/apply-important-selector.test.js
+++ b/tests/util/apply-important-selector.test.js
@@ -16,6 +16,10 @@ it.each`
   ${':is(.foo) :is(.bar)'}                      | ${'#app :is(:is(.foo) :is(.bar))'}
   ${':is(.foo)::before'}                        | ${'#app :is(.foo)::before'}
   ${'.foo:before'}                              | ${'#app :is(.foo):before'}
+  ${'.foo::some-uknown-pseudo'}                 | ${'#app :is(.foo)::some-uknown-pseudo'}
+  ${'.foo::some-uknown-pseudo:hover'}           | ${'#app :is(.foo)::some-uknown-pseudo:hover'}
+  ${'.foo:focus::some-uknown-pseudo:hover'}     | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'}
+  ${'.foo:hover::some-uknown-pseudo:focus'}     | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'}
 `('should generate "$after" from "$before"', ({ before, after }) => {
   expect(applyImportantSelector(before, '#app')).toEqual(after)
 })