brandonmcconnell / tailwindcss-signals

Signals for Tailwind CSS simplifies styling based on ancestor state via style queries. Its declarative API for signaling states eliminates complex selectors, resulting in cleaner, more maintainable code.
MIT License
772 stars 10 forks source link

Build in specificity-based sorting mechanism #2

Open brandonmcconnell opened 7 months ago

brandonmcconnell commented 7 months ago

Currently, the priority of conflicting signal-triggered styles relies solely on the order in which they are processed and appear in the output stylesheet. This lack of proper prioritization results in variable results when working with conflicting styles and makes using them for the same style unreliable.

It's an edge case, and even Tailwind CSS core doesn't support built-in prioritization beyond the ! symbol for importance, but they don't need to for this case because Tailwind CSS leverages the built-in specificity algorithm for style prioritization.

Tailwind CSS does, however, deal with something similar with their media query sorting internally. A similar convention could work here. I see a couple of options here:

  1. A more straightforward solution could be to order the signals based on the number of variants used to trigger them. In the example below, the container query for each signal would be ordered based on the specificity of the condition used when declaring it.

    <input type="checkbox" class="peer" /> 👈🏼 check/uncheck here
    <div class="signal/vader hover:signal/obiwan peer-checked:signal/yoda peer-checked:hover:signal/macewindu">
     <div class="signal/macewindu:!bg-purple signal/obiwan:bg-blue signal/vader:bg-red signal/yoda:bg-green">or hover here</div>
    </div>

    One issue with this approach that is core to the principle of Signals for Tailwind CSS is that signals must be trigger-able in multiple ways and places, so it might be unclear which usage is the most specific in certain instances. Even with that approach, certain signals might require different specificities in different places.

    This option would be the closest to how a natural CSS solution would work, similar to how group works, and this should maintain a 1:1 complementary relationship to that as much as possible.

    One last missing step that may ultimately solve this issue is to create a new media query not only per name but also per declaration, allowing each declaration to be uniquely sorted based on its specificity.

  2. Another option could be to use the modifier syntax as a manual prioritization system instead, so someone could use any of these:

    • signal:vader (specificity defaults to 0)
    • hover:signal/1:obiwan
    • peer-checked:signal/2:yoda
    • peer-checked:hover:signal/3:macewindu

    With this solution, I would expose signals as BOTH a variant and a utility, but both would have the same functionality of setting up the signal. Then, to consume a signal, you would use a different accompanying utility, perhaps effect as suggested by @AlexVipond in #1. This would solve the issue, but I don't love this solution to the problem and how it places the full burden of manual specification into the hands of the users. (I might still rename the variant to effect though to better align with the prior art behind signals in JS)

  3. I could ideate on some way to leverage CSS custom property inheritance here, but that idea is half-developed, and I don't think it would apply here since I'm working primarily with container queries and not variable values.
  4. Lastly, I do nothing and suggest that users implement an approach for handling conflicting utility classes, such as tailwind-merge (or similar)

Action items

For now, with this all at front of mind, I think the ideal solution would be Option 1, specifically the idea to set up a new container query per usage/unique selector sorted based on that selector's specificity.

AlexVipond commented 7 months ago

I noticed this, and yeah it's a tricky problem.

Variant chaining is the built-in adequate solution IMO. signal:bg-blue-100 signal:signal/one:bg-blue-200 signal:signal/one:signal/two:bg-blue-300 would override background color as expected. This is how people currently use group-hover:group-focus, and it's intuitive once you understand that CSS class usage order doesn't correlate with cascade order.