Skip to content

Conversation

evnchn
Copy link
Collaborator

@evnchn evnchn commented Sep 24, 2025

Motivation

Fix #5156, in which we see that Tailwind always loses in the CSS importance battle if Tailwind's layer is higher than Quasar.

  • Tailwind loses to Quasar's !important if Tailwind isn't begin with !
  • Tailwind still loses to Quasar since layer order is reversed if both rules are !important

That's no good. In addition, testing at #5156 (comment) reveals the new behaviour diverges from the old, which is not what we want either.

We did try #5166, that didn't work (NiceGUI-native documentation site is broken), while #5167 also didn't work (Quasar-native documentation site shall be broken)

Real motivation: Want 3.0 ASAP

Real motivation 2: Check the pipeline results to see if this broke anything

Real motivation 3: I look like a 🤡 in #5166

Implementation

  • tailwind_importants layer before all layers, normally empty and does not affect page operation
  • moveRules to be ran, if we detect any changes in <head>
  • moveRules to move all !important rules from Tailwind away from utilities into tailwind_importants so that accounting for the inverse of layer priority, Tailwind wins.

Progress

  • I chose a meaningful title that completes the sentence: "If applied, this PR will..."
  • The implementation is complete.
  • Pytests have been added (or are not necessary).
    • I'm considering it.
  • Documentation has been added (or is not necessary).
    • May need document this to keep users in-the-loop of what is happening

Comprehensive test script

Combines #5156 (comment) and #5167 (comment)

@ui.page('/')
def index():
    ui.label('Tailwind & Quasar CSS Comprehensive Test Script').classes('text-2xl')

    ui.label('I want a red button').classes('text-lg')

    ui.button('button').classes('bg-red-700')  # doesn't work in 2.x and 3.0.0rc1 and this PR
    ui.button('button').classes('!bg-red-700')  # doesn't work in 3.0.0rc1, but work in 2.x and this PR

    ui.label('I want "Hi!" to be visible only in dark mode').classes('text-lg')

    dark = ui.dark_mode()
    ui.switch().bind_value(dark, 'value')
    ui.label('Using invisible dark:visible')
    ui.label('Hi!').classes('invisible dark:visible')  # doesn't work in 2.x and 3.0.0rc1 and this PR
    ui.label('Using invisible dark:!visible')
    ui.label('Hi!').classes('invisible dark:!visible')  # doesn't work in 3.0.0rc1, but work in 2.x and this PR

    ui.label('If it is green, Tailwind is winning over Quasar').classes('text-lg')

    ui.label('Quasar Red, Tailwind (no important) Green')
    ui.button('button').props('color="red-10"').classes('bg-green-700')  # never wins

    ui.label('Quasar Red, Tailwind (with important) Green')
    ui.button('button').props('color="red-10"').classes('!bg-green-700')  # wins in 2.x and this PR, but not in 3.0.0rc1

Results

image

Documentation page screenshot

image

Additional notes

The value of UnoCSS (#4832) is that we can potentially do the same without the MutationObserver.

@falkoschindler falkoschindler linked an issue Sep 24, 2025 that may be closed by this pull request
3 tasks
@falkoschindler
Copy link
Contributor

Interesting approach, @evnchn!

It feels a bit sketchy to tear apart Tailwind CSS rules, but it's certainly better then what we tried before.

I'm a bit concerned about the performance implications of observing and re-processing the document head. That's why I tried moving Quasar rules instead (they don't change). It took me quite a while, but this seems to do the job:

@layer theme, base, quasar, nicegui, components, utilities, overrides, quasar_importants;
...
@layer quasar_importants {
  /* Do not remove this layer, it is used in the JS to move !important rules from the "quasar" layer here */
}
const rules = Array.from(document.styleSheets[0].cssRules);
const source = rules.find((r) => r.layerName === "quasar").styleSheet;
const target = rules.find((r) => r.name === "quasar_importants");
for (let i = source.cssRules.length - 1; i >= 0; i--) {
  const rule = source.cssRules[i];
  if (rule instanceof CSSStyleRule && /!important/.test(rule.cssText)) {
    source.deleteRule(i);
    target.insertRule(rule.cssText);
  }
}

It just bugs me that this snippet randomly jumps between taking 4ms and 800ms. Still investigating...

@falkoschindler
Copy link
Contributor

Here we go:

const rootSheet = document.styleSheets[0];
const rules = Array.from(rootSheet.cssRules);
const source = rules.find((r) => r.layerName === "quasar").styleSheet;
const target = rules.find((r) => r.name === "quasar_importants");
rootSheet.ownerNode.disabled = true;
for (let i = source.cssRules.length - 1; i >= 0; i--) {
  const rule = source.cssRules[i];
  if (rule instanceof CSSStyleRule && /!important/.test(rule.cssText)) {
    source.deleteRule(i);
    target.insertRule(rule.cssText);
  }
}
rootSheet.ownerNode.disabled = false;

Now it always takes around 5ms. The trick is to avoid re-rendering while working on the CSS sheet.

@evnchn
Copy link
Collaborator Author

evnchn commented Sep 24, 2025

@falkoschindler Awesome idea to move Quasar's importants to a less-important layer than to move Tailwind's importants to a more-important layer (considering importance after !important order reversal), since Quasar styles do not change (in general, I think? I can't exclude possibility of Quasar setting style via JS but if they do I will be mad)

But why do it in the browser? Can't we have:

      {% if prod_js %}
        @import url("{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.prod.css") layer(quasar);
        @import url("{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.importants.prod.css") layer(quasar_importants);
      {% else %}
        @import url("{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.css") layer(quasar);
        @import url("{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.importants.css") layer(quasar_importants);
      {% endif %}

Assuming you have created the quasar.importants*.css files somewhere else.

@falkoschindler
Copy link
Contributor

@evnchn Good idea! It was easier to do it in JS in the browser, but it should be possible as a preprocessing step, ideally in extract_core_libraries.py.

@evnchn
Copy link
Collaborator Author

evnchn commented Sep 24, 2025

Heck, I even think that the segregation of important and no-important styles can be officially in Quasar, offering advanced technical people (like us, integrating Quasar into NiceGUI) the flexibility, while everybody else can just import 2 stylesheets / 1 combined stylesheet and get along with their normal usage.

Can propose later if it works here, I guess

@falkoschindler
Copy link
Contributor

Not sure if they like it, but worth a try.

Two caveats with the current implementation:

  • The script moves whole rules like .q-icon::after, .q-icon::before { width: 100%; height: 100%; align-items: center; justify-content: center; display: flex !important; }, including non-important declarations. We might want to extract the important ones and leave the non-important ones in place.
  • The script only handles instances of CSSStyleRule and skips rules like
    @media (orientation: landscape) {
      .orientation-portrait { display: none !important; }
    }

@evnchn
Copy link
Collaborator Author

evnchn commented Sep 24, 2025

image

Works

@evnchn
Copy link
Collaborator Author

evnchn commented Sep 24, 2025

Wait but does this capture and move this?

image

@evnchn
Copy link
Collaborator Author

evnchn commented Sep 24, 2025

This fails both of our implementations I fixed it on mine 🤪

from nicegui import ui


@ui.page('/')
def page():
    ui.label('This text is always visible')
    # Quasar: print-only is hidden on screen, visible on print
    ui.label('print-only').classes('print-only')

    # Test if user's custom important CSS can override Quasar's print-only class
    ui.add_css('''
    @layer utilities {
        @media screen {
            .print-only {
                display: block !important;
            }
        }
    }''')


ui.run(show=False, port=9999)

@falkoschindler
Copy link
Contributor

Let's close this PR in favor of PR #5171.

@evnchn evnchn deleted the moverules-tailwindcss branch September 26, 2025 22:43
falkoschindler added a commit that referenced this pull request Sep 29, 2025
### Motivation

Fix #5156, in which we see that Tailwind always loses in the CSS
importance battle if Tailwind's layer is higher than Quasar.

- Tailwind loses to Quasar's `!important` if Tailwind isn't begin with
`!`
- Tailwind still loses to Quasar since layer order is reversed if both
rules are `!important`

That's no good. In addition, testing at
#5156 (comment)
reveals the new behaviour diverges from the old, which is not what we
want either.

We did try #5166, that didn't work (NiceGUI-native documentation site is
broken), while #5167 also didn't work (Quasar-native documentation site
shall be broken). #5170 we're almost there but as said in
#5170 (comment)
we should instead segregate the Quasar styles in the server side Python
pre-comute.

**Real motivation: Want 3.0 ASAP**

**Real motivation 2: Check the pipeline results to see if this broke
anything**

**Real motivation 3: I look like a 🤡 in #5166**

**Real motivation 4: #5170 is not adopted due to frequent updates on
head**

### Implementation

`quasar_segregator.py` creates the 3 CSS files. 

### Progress

- [x] I chose a meaningful title that completes the sentence: "If
applied, this PR will..."
- [x] The implementation is complete.
  - [x] Separate the mixed ones. I've been stuck for a while
  - [x] Modify the `index.html` to serve these segregated styles
  - [x] Minify with `rcssmin`
  - [x] Beautify with `cssbeautifier`
  - [x] Document pip install commands
- [x] Pytests have been added (or are not necessary).
  - Again I'm considering it. 
- [x] Documentation has been added (or is not necessary).
- We should probably also document this somewhere just for avoid
confusion down the line.
- [x] fix comments being moved to wrong code block
- [x] integrate with extract_core_libraries.py
- [x] add new dev dependencies to pyproject.toml

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't apply TailwindCSS classes forcefully in NiceGUI 3.0.0rc1

2 participants