w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.52k stars 672 forks source link

[css-conditional] Element queries #10509

Open romainmenke opened 4 months ago

romainmenke commented 4 months ago

Container queries allow us to query a container for specific conditions, including values of custom properties through style() queries.

But they do not allow us to query the element itself.

In https://github.com/w3c/csswg-drafts/issues/10064 there is a proposal for inline conditions that do allow setting values conditionally when an element has a specific value. Implementers indicated that this would be possible

Can we have the same as an at rule?

I am assuming that this needs to be highly restricted as explained in #10064 (only style() queries with custom properties?)

.foo {
  background-color: blue;
  color: white;
  border-radius: 2px;

  @element style(--color: pink) {
    background-color: pink;
    color: black;
    border-radius: 4px;
  }
}

This example would result in a pink background on any element with class .foo when it also has --color: pink.


Nested vs. not nested gives interesting possibilities:

/* style multiple variations for `.foo`: */
.foo {
  background-color: blue;

  @element style(--color: pink) {
    background-color: pink;
  }

  @element style(--color: green) {
    background-color: green;
  }
}

/* style multiple variable elements: */
@element style(--color: green) {
  .foo {
    background-color: green;
  }

  .bar {
   color: green;
  }
}

Benefits I see to this syntax:

mirisuzanne commented 4 months ago

Re-targeted this against css-conditional, since container queries are being moved over there, and out of the contain spec.

If we can do something like this, it would be useful. I'd be curious about trying to integrate it more into container queries - but I'm not sure if that only makes it more complicated.

The primary limitation here that CQs solve is the potential for dependency loops. As I understand it, that's easier to resolve property by property (with the if() function). If we create a loop with this syntax, does it invalidate the entire block?

.foo {
  --color: pink;
  background-color: blue;

  @element style(--color: pink) {
    --color: not-pink; /* certainly this is invalid */
    background-color: pink; /* but is this applied? */
  }
}

I imagine we can come up with an answer to that question, as far as feature design goes. But I'm not sure how hard it is to implement.

kizu commented 4 months ago

My thinking: we already can have multiple ways to have inline conditionals: https://lea.verou.me/blog/2024/css-conditionals-now, and some of them working almost as “block”-level ones as well (the @keyframes one), and then at some point we will have proper inline conditionals (as already mentioned), and any block-level conditional can be expressed as an inline one, a block-level @element () (or @if, in case we'd want to align it with the inline one) could be purely a syntax sugar and a shortcut.

Of course, that is if we'd treat them the same.

If we create a loop with this syntax, does it invalidate the entire block?

If we were to work around such conditionals with inline ones, any IACVT variables used for that condition will result in everything inside becoming also IACVT.

Then, if we will have something like !revertable (https://github.com/w3c/csswg-drafts/issues/10443) (and in some cases possible to work around today with revert-layer (https://kizu.dev/layered-toggles/)), instead of completely nuking all the affected properties we could just fall back to the previous declarations.

Interestingly, only the element itself will be affected in this way: any nested styles could already be expressed as container queries, which properly handle reverting styles when not applying.

.foo {
  --color: pink; /* becomes IACVT */
  @element style(--color: pink) {
    --color: not-pink;
    & .bar {
        background-color: pink;
    }
  }
}

In the above block, authors could separate the & .bar into a container query, which could be cumbersome, but will work as intended. I don't think there is any other way it should behave for @element, as there is no better behavior than just not applying styles similar to CQ here.

Thus, my proposal is: if an element query creates a loop in a condition, all affected styles on the element itself should be treated as !revertable, and any nested styles should be treated as covered by the same container query.

mirisuzanne commented 1 month ago

I wonder if there would be a path for adding something like @bkardell's available-inline-space keyword (see All Them Switches) to the inline if() as a step in this direction? We could focus on the features needed for the inline solution, and then see if there's a good path to making it block-level as well?

@kizu You're suggesting that de-sugared if() statements would already imply:

That sounds reasonable to me, though I'm not sure if that's already part of if(). Does that proposal currently query the same element, or does it look at the parent?

I think it's probably good that your proposal would invalidate the entire block – so the rule block is all-or-nothing – but doing it at computed value time does seem rough unless we have something like !revertable.

Other query types (like size queries) might be a bit more complicated? We would have to disallow changing anything that could impact size in any way – which isn't always a clear or concise restriction.