w3c / csswg-drafts

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

[css-values] Add `valid-empty`-like value (for CSS Custom Properties) #10441

Open raphaelokon opened 5 months ago

raphaelokon commented 5 months ago

There was a brief conversation (I think between @kizu and @andruud) I participated during CSS Day regarding a declarative keyword to mark the guaranteed absence of a value for a custom property to avoid the space toggle hack, e.g.

.meow {
  --foo: ; /* <- One whitespace character here. */

  color: var(--foo, hotpink) /* Resolves to `--foo` */
}

would become →

.meow {
  --foo: valid-empty; /* <- Explicitly mark missing value as valid. */

  color: var(--foo, hotpink) /* Resolves to `--foo` */
}

I think that the inline conditional or if() spec #10064 by @LeaVerou (which was just marked as resolved 🎉) may fix a lot of use-cases around this, but there maybe still be use-cases for a declarative empty value.

EDIT: Change the semantic of the keyword and make it less generic by naming it valid-empty re comment.

kizu commented 5 months ago

I just want to clarify that for

  --foo: empty; /* <- Declarative absence of a value. */
  color: var(--foo, hotpink) /* Resolves to `hotpink` */

It will still resolve to --foo, just explicitly.

A good use case outside the space toggles is when we want to have a “placeholder” of sorts inside a shorthand. I wrote a post with one example like that here: https://blog.kizu.dev/space-toggle-placeholders/

Re: the name itself, as this will be a CSS-wide keyword, and essentially allowed anywhere, we need something less generic than empty. Maybe something like guaranteed-valid, valid-empty? empty-placeholder? Something else?

The goal of the value is to make the declarations like --foo: guaranteed-valid or var(--foo, guaranteed-valid) more explicit and easy to understand compared to --foo: ; or var(--foo,).

raphaelokon commented 5 months ago

Ah, now I see, I missed this detail. In this regard I really like the valid-empty keyword. I’ll edit it If that is okay for you?

LeaVerou commented 5 months ago

Or it could be syntactically distinct from other keywords somehow.

raphaelokon commented 5 months ago

Or it could be syntactically distinct from other keywords somehow.

@LeaVerou This one is interesting as well and opens other possibilities. Any ideas what would be syntactically possible?

What I like about @kizu’s suggestion valid-empty (as opposed to guaranteed-valid) is that the spec describes values being guaranteed-invalid and not the value itself. In this understanding the --foo: ; and --foo: valid-empty; keyword would be a guaranteed-valid values.

raphaelokon commented 5 months ago

@LeaVerou I’ve been thinking about other syntaxes as well. We probably have to define if there are use-cases outside of custom props for this keyword.

Some high-level ideas (not happy with them) working around CSS’s case-insensitivity (I still like the explicit nature of valid-empty):

.meow {
  --foo: ();
  --baz: !valid;
  --bar: *;
}
Loirooriol commented 5 months ago

I don't get it.

raphaelokon commented 5 months ago

@Loirooriol

tl;dr Make a valid empty custom prop (or a space variable placeholder for that matter) more readable and explicit.

Why is it a hack to use a space?

That is the name you can find it under and the CSS community refers to when using it.

Note you don't even need a space, --foo:; is valid too.

Aye, I know. That is true, but that does not make the readability issue any better.

Would valid-empty just behave as a valid sequence of no tokens?

I am not sure I can answer this. But in my opinion valid-empty would be a CSS-wide keyword.

Why would this be useful?

To me having an explicit keyword to set the value of a custom property to guaranteed-valid would be consistent in how you manually reset a custom property to guaranteed-invalid by using initial keyword.

If you dislike empty values, why not just use --foo: /valid-empty/;?

I am not disliking it, it is more to have an explicit keyword. But having comments looks even more hackier to me.

EDIT: Fix typo.

kizu commented 5 months ago

If I recall correctly, at some point --foo:; (and var(--foo,) — without a space) was not valid. Right now it is, yes.

My main argument is learnability and discoverability: when you see something like this in your code or devtools:

A screenshot of chrome devtools showing a tooltip with an empty space (there is a tooltip that looks empty)

You just see an empty value, which does not tell you anything. You could put the /* empty */ in the variable definition, but used value somewhere else won't have it. And then CSS minifiers would likely remove the comment, making the source less undersrandable again.

When you discover something like that in the wild, and you're an author who does not know about this valid empty value, how would you search for it? A query like “css empty value” won't help, but “css empty variable value” would, but not everyone is good at search, sadly. It is much easier to look for something like “css valid-empty” when you know the keyword.

Loirooriol commented 5 months ago

CSS-wide keywords should work everywhere, would you accept color: valid-empty?

I also don't get why you would want color: var(--foo, hotpink) to be IACVT instead of using the fallback.

And an empty tooltip in devtools doesn't seem cunfusing to me, if the value is empty.

LeaVerou commented 5 months ago

FWIW I completely agree with @Loirooriol: I think an actual empty value is the most self-explanatory value we could ever come up with. It’s literally WYSIWYG!

@kizu It seems that it's the empty tooltip that's the problem here, not the syntax. Dev tools could easily show something like "(Empty value)" to make this clear.

raphaelokon commented 5 months ago

@Loirooriol

CSS-wide keywords should work everywhere, would you accept color: valid-empty?

That is a good point tbh. Maybe only scoping the value keyword to custom properties -- and var() as in --foo: valid-empty; or var(--foo, valid-empty). Maybe could help with #9847 as well.

I also don't get why you would want color: var(--foo, hotpink) to be IACVT instead of using the fallback.

Just to show the same behavior of an empty value string and valid-empty here, no practical reason.

@LeaVerou @kizu Regarding dev tools, I get this tooltip behavior →

Screenie_2024-06-16-6oL6Jd5N@2x

I am also not able to manually set --bar: ; via the style inspector, but that may be a vendor issue.

yisibl commented 5 months ago

@raphaelokon It looks like this is a bug in Chrome DevTools, please file a bug.

raphaelokon commented 5 months ago

@Loirooriol Another thing I noticed when registering a custom property with CSS and JS when defining an intitial-value with a single space →

@property --foo {
  syntax: "*";
  inherits: false;
  initial-value: ; /* Single space here …*/
}
window.CSS.registerProperty({
  name: "--meow",
  syntax: "*",
  inherits: false,
  initialValue: " ", /* Single space here …*/
});

When getting the value of the custom props we get different value behavior.

const styles = window.getComputedStyle(document.documentElement);
console.log(styles.getPropertyValue("--foo")?.length); // => 0
console.log(styles.getPropertyValue("--meow")?.length); // → 1

Screenie_2024-06-17-C2ieNg5D@2x

raphaelokon commented 5 months ago

There is also a mention of a no-value keyword in the inline conditional if() in #10064.

Edit: Add issue ref.

Loirooriol commented 5 months ago

@raphaelokon That's a bug. https://drafts.css-houdini.org/css-properties-values-api/#the-registerproperty-function

parse initialValue as a <declaration-value>.

raphaelokon commented 5 months ago

@yisibl I’ll file a Chrome DevTools bug.

@Loirooriol Is there already a bug filed for it in CSS Houdini or should I file one?

Loirooriol commented 5 months ago

I meant implementation bug. But actually not, it's just parse a declaration what trims whitespace. But I do think <declaration-value> should trim too as said in https://github.com/w3c/csswg-drafts/issues/6871#issuecomment-990127688

Loirooriol commented 2 months ago

But having comments looks even more hackier to me

If you don't want to use a "hacky" space/comment everywhere, you could also use an auxiliary variable, with an explanation of what's going on, and then just let all the consumers reference it.


:root {
  /* This sets `--valid-empty` to an empty value, this allows setting `--foo: var(--valid-empty)`
     so that `var(--foo, hotpink)` resolves to an empty value instead of falling back to `hotpink`
     or becoming invalid at computed-value time. */
  --valid-empty: ;
}
.meow {
  --foo: var(--valid-empty);  /* <- Explicitly mark missing value as valid. */
  color: var(--foo, hotpink); /* Resolves to `--foo` */
}