w3c / csswg-drafts

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

[css-variables] User Agent properties and variables #1693

Closed grorg closed 3 years ago

grorg commented 7 years ago

Here is WebKit's proposal for a new type of property and variable.

User Agent Properties

This specification defines an open-ended set of properties called User Agent properties, which, among other things, are used to define the substitution value of constant() functions.

Name: (various) Value: <declaration-value> Initial: (nothing, see prose) Applies to: all elements Inherited: yes Percentages: n/a Media: all Computed value: specified value with variables substituted (but see prose for "invalid variables") Canonical order: per grammar Animatable: no

A User Agent property is not specified in a style sheet. User Agent properties instead define variables, referenced with the constant() notation. For example, a page that wants to use the user's requested font sizing, and page background:

:root {
  font-size: constant(user-font-size);
}

body {
  background-color: constant(user-background-color);
}

Unlike other CSS properties, User Agent property names are case-sensitive.

User Agent properties are not reset by the all property. If a style rule attempts to define a value for a User Agent property, it is ignored.

Using Cascading User Agent Variables: the 'constant()' notation

The value of a User Agent property can be substituted into the value of another property with the constant() function. The syntax of constant() is:

constant() = constant( <user-agent-property-name> [, <declaration-value> ]? )

The constant() function is used in the same manner, and follows the same rules, as the var() function.

Defined User Agent Variables

diligiant commented 7 years ago

@jakearchibald I won’t contradict you :) Do you treat the keyboard overlay similarly?

The drawback being it can’t be handled with pure CSS.

jakearchibald commented 7 years ago

@diligiant On Android & iOS, the keyboard just reduces the viewport size, you can still scroll down to the area that the keyboard may have obscured. This means visualViewport will tell you about it. This works in Chrome today.

When @grorg mentioned "overlay keyboards", I took that to mean something that overlays the viewport, so even if you scroll right to the bottom of the page, the keyboard will still be obscuring content. In this case constant(safe-area-inset-bottom) could give you the amount of padding you can apply to the bottom of the page to overcome this.

diligiant commented 7 years ago

@jakearchibald I'll be very interested to read @grorg explanations as I have the feeling safe-area-inset-* and visualViewport are tackling different issues (highlighted by the fact that the former is css-oriented and the latter being js-oriented.)

jakearchibald commented 7 years ago

@diligiant

I have the feeling safe-area-inset-* and visualViewport are tackling different issues

We agree!

If it's something that changes the size/position of the viewport, such as a keyboard, a URL bar, or a pinch-zoom, visualViewport is what you're looking for. It is very very rare to need to change layout as the result of a viewport change (details).

If it's something that obscures content but does not change the viewport, such as physical screen shape, overscan, a picture-in-picture overlay, or an overlaying keyboard, the proposed solution is viewport-fit and safe-area-inset-*.

See https://developers.google.com/web/updates/2017/09/visual-viewport-api#layout_viewport_vs_visual_viewport for a description of the layout vs visual viewport. The iOS & Android keyboard changes the visual viewport, whereas the notch requires the user to adjust layout so content moves out of its way.

tabatkins commented 7 years ago

Again, can we please move discussion about the new iPhone's notch/corners (which isn't specific to the iPhone anyway) to another issue. It's not directly germane to the general discussion of UA-defined constants/globals.

othermaciej commented 7 years ago

Should there be a standardized set of constant names with an expected meaning, or is that all up to the UA? I believe @grorg's pull request only defines the mechanism, but not any specific constant names.

jakearchibald commented 7 years ago

@othermaciej I think the names Apple intends to use should be standardised. Otherwise, it's all the problems of vendor prefixes without the minor benefit of an actual vendor prefix.

FWIW the proposed naming seems good.

grorg commented 7 years ago

@diligiant

IMHO unless Safari supports CSS Level 4 min/max at the same time, it will be tough to accommodate the complex positioning rules we have today.

Yep, we agree. See https://bugs.webkit.org/show_bug.cgi?id=167000 😄

FremyCompany commented 7 years ago

Replying to @tabatkins

I have got a problem here, and my problem varies ;) More precisely, the values that we are discussing here are not going to be constants.

Yeah, what eeeps said - they're constant over the page, as opposed to refs that can vary from element to element.

All in all, I have not seen any argument to convince me we should invest in adding a new function instead of a namespace for existing value references.

A big difference is that these are usable in places other than properties and animations - you should be able to use these "constants" in MQs and such. (Again, because they're constant across the page, and so don't care about what context they're being used in.) I'm loathe to say "var() is only allowed on elements normally, but you can use it wherever you want if you use this particular type of var name" - much better for everyone if it's a totally distinct name.

Ok, so I gave this another round of thoughts, and I agree that I can see the value in having a different function name to make it clear the places where the replacement will work are not the same. I still think this isn't needed, but I can see the value.

I really do not like the name constant(...) though. Really, there is no such thing as "constant at any given time". If you look at the definitions of "constant", you get either of these which both do not apply here (because the value of the variable can change over time):

adj: remaining the same over a period of time noun: a situation or state of affairs that does not change

The other thing is that this is utterly verbose. If you have to use this multiple times in a calculation, having to type constant() is really annoying and space-filling, reducing gimpsability quite a lot.

I remember that when var(...) was introduced, we ruled out $... because it would conflict with css preprocessors, then also ruled out $(...) because Tab wanted to use it for things that would replace to the same thing globally.

I think this proposal matches that description: some value replacement that happens to be globally the same (but could be globally invalidated as a result of a value change). I would therefore propose we rename these variables to "globals" instead of "constants" and use $(...) to refer to them anywhere a css value is allowed.

Opinions?

bkardell commented 7 years ago

Hmm... Are you at so worried that $ here could lead to even more confusion?

tomhodgins commented 7 years ago

To me, the term constant() feels like a misnomer, especially if the rule (and property and value) can be overriden by a more specific selector.

body {
  color: constant(fontColor);
}
body.home {
  color: red;
}

It's not looking "constant" in any sense of the word, plus it's confusing on another level with var and const in JavaScript existing for similar uses, but different to each other and also different to variable() and the proposed constant().

The $() syntax doesn't feel quite like anything else in CSS. The first thing that pops into my mind would be something like global() or ua() standing for user-agent. Consider you had never heard of the functionality of exposing browser settings to CSS before, which of the following syntax examples conjures up the idea that that's what's going on in the code:

body {
  color: constant(fontColor);
  background: constant(backgroundColor);
}

or

body {
  color: $(fontColor);
  background $(backgroundColor);
}

or

body {
  color: browser(fontColor);
  background: browser(backgroundColor);
}

Given these examples, the presence of constant() without any other context makes it sound like an invariable…variable? What's that? It doesn't really make sense, if you wanted it constantly one colour couldn't you just set that in the CSS all the time? It's so wrong it feels nearly self-negating.

The second example, using $() reads to me like a layer on top of CSS - either a preprocessor variable or something that's going to be interpolated before that text becomes CSS. This is closer to what's really going on, but my first thought is not that it's coming from the browser. If I saw that in the wild my first thought would be that somebody accidentally published some kind of Sass/Less/Stylus-like preprocessor dialect without compiling it to CSS first. My initial thought would be to google what preprocessor uses $() as a syntax so I could compile it properly.

But given the third example, browser(), even if you have no idea that the functionality is possible, it's CSS-like enough that it could be a native feature, and the name browser() hints that it's nothing that could be rendered in advance…before the browser. For me at least, I think this is not only elegant enough to use while authoring CSS, but also provides the correct context to help people intuit what is going on in the code, as well as provide something 'googleable' they can look up to find out more.

That's my 2¢. This is a great idea for a feature, getting the naming right (or wrong) could be the difference between empowering authors and bewildering them.

devatrox commented 7 years ago

Why not follow the idea of currentColor?

body {
    color: userForegroundColor;
}
jakearchibald commented 7 years ago

currentColor isn't global, so it isn't really the same.

devatrox commented 7 years ago

I get that, but it is a predefined value that the dev can't directly manipulate (indirectly he can in this case). The values proposed here can also change constantly (no pun intended) depending on device orientation, environment, settings, whatever, so it is merely an alias to some value defined in the UA.

The difference between currentColor and userForegroundColor is just the scope, as one uses this and the other window

EDIT: The newly proposed system-ui "font" or simply sans-serif would be another example that is, in my view, the same thing as we are discussing here. currentColor might have been the wrong example to use here because it's distracting.

FremyCompany commented 7 years ago

Why not follow the idea of currentColor/system-ui/...?

Because a browser needs to track all these values one by one, and that is costly in terms of code to write and combinations to test. The advantage of reusing the infrastructure of var(...) is that it is generic and can be detected very easily at parse time.

FremyCompany commented 7 years ago

The second example, using $() reads to me like a layer on top of CSS - either a preprocessor variable or something that's going to be interpolated before that text becomes CSS. This is closer to what's really going on, but my first thought is not that it's coming from the browser.

If I understood @tabatkins properly, while some of these values will come from the browser, some can also come from JavaScript using the proposed $(--my-global) syntax. If I misunderstood that then my point is moot, of course,

If I saw $(...) in the wild my first thought would be that somebody accidentally published some kind of Sass/Less/Stylus-like preprocessor dialect without compiling it to CSS first.

Well, it's because you don't know this is allowed in CSS. That's something you would learn as part of learning CSS in the future even before you start learning about SASS.

tabatkins commented 7 years ago

Should there be a standardized set of constant names with an expected meaning, or is that all up to the UA? I believe @grorg's pull request only defines the mechanism, but not any specific constant names.

Yes, this isn't even a question. Without standardization, this is just vendor-specific, and thus worthless. (And thus should be protected behind whatever your vendor-specific-code mechanism is: prefixes or flags or what-have-you.)


Why not follow the idea of currentColor?

Because keywords need to be tracked and monitored for collisions; there are places where we accept arbitrary keywords (like animation-name) where we explicitly handle the few built-in keywords (and thus implicitly disallow you from using them as animation names/etc). Every time we add a new built-in keyword, we run the risk of breaking author code that is currently using it as a custom keyword.

A function, on the other hand, can be handled globally without collision; we don't and will never allow user-defined functions with arbitrary names (they'll be -- prefixed like other custom things), so there's no worry about breaking author-level code.


I think this proposal matches that description: some value replacement that happens to be globally the same (but could be globally invalidated as a result of a value change). I would therefore propose we rename these variables to "globals" instead of "constants" and use $(...) to refer to them anywhere a css value is allowed.

I'm fine with this; it does indeed match the use-case I was trying to reserve $ for. It means some Syntax changes, tho. ^_^

zhouqicf commented 7 years ago

Why the constant can't be used in calc? This does not meet expectations.

Yes, we can use height: 44px; padding-top: constant(safe-area-inset-top), but height: calc(constant(safe-area-inset-top)+44px) is conciser, and sometimes we have no choice.

Because i using calc() with no spaces around the +, constant can be used in calc.

dpogue commented 7 years ago

Why the constant can't be used in calc? This does not meet expectations.

The constants do work in calc(), at least in my testing in Safari on the iPhone X simulator.

header {
  height: calc(44px + constant(safe-area-inset-top));
}

They also work in gradients:

linear-gradient(to bottom, black 0%, black constant(safe-area-inset-top), grey constant(safe-area-inset-top), grey 100%);
css-meeting-bot commented 7 years ago

The Working Group just discussed User Agent properties and variables, and agreed to the following resolutions:

The full IRC log of that discussion <dael> Topic: User Agent properties and variables
<dael> github: https://github.com/w3c/csswg-drafts/issues/1693
<dael> smfr: Apple is proposing constantst which are UA defined variables. They're supplied by the UA and not assignable in CSS. Work everywhere variables work. We intend them for metrics that are of interest to web authors to layout their pages. You can imagine one for things like scrollbar.
<dael> smfr: Ones we're most interested in is iPhone 10 where it tells authors how to avoid rounded corners.
<dael> smfr: Two aspects to this discussion. Is the constant function the right name. Second part is should we have a specific set and list them out?
<dael> TabAtkins: Hitting hte second one. You cannot have this as a feature and have a bunch of UA specific values. It doesn't help authors
<dael> smfr: I agree
<dael> TabAtkins: Values need to be in spec. You can have UA ones behind a flag, but general set of values need to be specified and interop.
<dael> smfr: We agree.
<Rossen_> Bert: please reply back to this email (https://lists.w3.org/Archives/Member/w3c-css-wg/2017JulSep/0160.html) and let us know what help you need from the rest of us
<dael> TabAtkins: I love this and this is great. One bit I think I disagree. This should be usable wider than variables. You should be able to use a global in a MQ or something. It shouldn't be limited to use in properties.
<dael> smfr: I think that's reasonable
<gregwhitworth> florian: you're really quiet
<dael> florian: I'm not sure. 2 reasons. If it's not like variables we need to decide how it works. We'd have to invent a new thing. Also, it's not crazy to imagine that this might have different values per element in the DOM, like what's the default font and that's language dependent so you need a DOM. I'd be careful about expanding it.
<dael> TabAtkins: That sort of thing would not be super compat with how we're thinking about this. BUt if we did something like that I would be fine to say in that case it's an invalid reference. Same as using color in a link MQ.
<dael> florian: Okay.
<dael> smfr: One concern with using this in MQ. WE found authors often want them in conjunction with min and max. So that gives you feature creep where you want min and max in MQ.
<dael> TabAtkins: Calc is allowed per spec, which you and then do min and max
<dael> smfr: Does any browser?
<dael> TabAtkins: Dunno.
<dael> smfr: If it's in spec, it's reasonable.
<dael> TabAtkins: You might as well be able to. It makes sense.
<dael> TabAtkins: Regarding names, I'm fine with constant, but thread had people object to that. They suggested global which was fine to me too.
<dael> TabAtkins: fremy also pointed out this is the use case I was trying to reserve $ for. So that might work.
<dael> smfr: I do'nt like $ b/c if someone doesn't know CSS it looks like magic. I think 'constant' is way easier.
<dael> florian: I think we want the extra param on it so if we have $ and function.
<tantek> "you can't google it" is an interesting critique of any new symbol-based syntax
<dael> TabAtkins: A function names $ so $(stuff)
<tantek> "dollarsign(stuff)?"
<Rossen_> dollar-sign()
<dael> fantasai: These are environment variables so you could use the entity.
<fantasai> env() because these are environment variables
<florian> +1 to env()
<dael> myles: Other programming languages let you use $ for things that do vary.
<dael> Chris: I quite like env option. It's an environment vairable.
<dael> TabAtkins: Expcet we want people be able to set the custom ones.
<dael> Chris: Ah.
<dael> TabAtkins: We recognized a lot of variables are a single global set at the root and the entire enharetence machine for that is a lot. We're working on If a variable is only set on the root we pop it in a seperate storage area. This would let authors declare this sort of thing.
<dael> TabAtkins: So you set it once and you have a global variable across the page.
<dael> smfr: So you want the same syntax for that and these?
<Rossen_> q?
<dael> TabAtkins: Names would have to be -- prefix to distinguish name space
<Bert> q-
<dael> fantasai: How to declare?
<Rossen_> Bert, yes let's take that over mail
<dael> TabAtkins: Options. Suggested JS API so you can monitor in JS and watch for changes so you can respond to them. If we have that structure it will give you read only access to these. We can allow write access too if it's in the right form. That's the basic.
<dael> fantasai: That's not a good answer.
<dael> TabAtkins: That's the basic.
<dael> fantasai: I don't consider that basic.
<dael> TabAtkins: Secondary is an @'function name' rule in your stylesheet. Give it a name and a value. It looks same as property, but as an @ rule. Only problem there is if you want to respond to changes we need to expose it in some JS API. So you either delve into CSSOM or we have to do some mapping between a JS object.
<dael> TabAtkins: I have suggestions to this in a thread from where dino is suggesting edits.
<dael> TabAtkins: Current idea is the big JS API can also be used from JS to demote both user and UA defined ones. @global lets you do it in stylesheets and they grow a similar map-like object that's a proxy. Therefore if you need to watch the values you can do so, but if you want to set up in JS you can do that. Whichever is best for you you have access to.
<dael> smfr: How does that impact the naming?
<dael> TabAtkins: Not at all. Whatever the function name is the @rule is similar.
<dael> smfr: Does it promote any of the options?
<dael> TabAtkins: I don't think so. Any of the ones we've come up with are available and reasonable to read
<bkardell_> lol
<dael> smfr: Can we try and resolve on a name?
<smfr> options: constant(), const(), env(), global()
<dael> Rossen_: I head env and global in the running
<dael> smfr: Let me type them.
<dael> smfr: Those (a few chats before)
<bkardell_> I will go against the grain and say I like const() actually
<dael> Rossen_: So constant and const since they do change can we rule those out?
<florian> prefers env, ok with global
<dael> fremy: global is what I prefer. I quite like the $, but of those global is most clear to authors.
<bkardell_> prefers env
<dael> Rossen_: I see some people saying env, some people global. Which is easier to understand for non-english?
<dael> ??: I voted for env
<dael> Chris: THe problem with env is it doesn't work for everything that TabAtkins does
<bkardell_> author-env variables like bash
<bkardell_> yes, what tab said !
<dael> TabAtkins: It's the same as for what bash uses.
<Chris> I like env
<dael> Rossen_: Let's try and resolve on env. Let's get this one on the books for now.
<dael> Rossen_: Objections to renaming what was constant variables to environment variables using env()?
<Chris> also can we please stop u-turning on names because it really impacts deployment
<dael> smfr: I'm not particularly happy because we have an impl, but I guess we can change.
<Chris> bikeshedding considered harmful
<dael> TabAtkins: Given there is 0 spec text, I think we'd be angry if you pull a we can't change due to impl.
<dael> RESOLVED: rename what was constant variables to environment variables using env()
<dael> Rossen_: Did we resolve on the list of predefined?
<dael> smfr: Well what spec do they go in?
<dael> fantasai: variables
<dael> Rossen_: variables l2?
<dael> TabAtkins: Either own or variables. variables 2 is good.
<dael> Rossen_: Makes most sense given how far variable spec is.
<dael> Rossen_: Obj adding this as ED of variable L2?
<tantek> seems reasonable
<fantasai> sgmt
<Chris> +1 to variables-2
<bkardell_> +1
<fantasai> s/sgmt/sgtm/
<dael> RESOLVED: Add this as an ED of variables L2
<dael> Rossen_: Editor?
<dael> smfr: I think I'd like to say dino will do it.
<dael> TabAtkins: He put text together so he self-volunteered
<dael> Rossen_: Obj?
<dael> RESOLVED: Add dino as an editor of variables L2 with TabAtkins
<dael> Rossen_: Now that we know what we're calling them and where, what will be the initial set of predefined values?
<dael> Rossen_: Should we take this back to github?
<dael> florian: I think so
<dael> smfr: We'd at least like safe area top, botoom, left, right
<dael> TabAtkins: Let's get a list in GH
<dael> fantasai: I think we can resolve on those 4 values.
<dael> s/botoom/bottom
<dael> Rossen_: Objections to adding the initial set as safe area top, bottom, left and right?
<dael> RESOLVED: The initial set as safe area top, bottom, left and right
tabatkins commented 7 years ago

@zhouqicf Your screenshots show you using calc() with no spaces around the +; that's an error, resulting in an invalid property. constant() will have the same basic semantics as var(), so it's usable anywhere var() is.

jonathantneal commented 7 years ago

I have a few questions.

First, I am presuming the request included making it clear “that there is no way for the developer to set these properties”. And now, I see interest in doing the opposite. which is to make these properties settable. I also see interest in making these properties exposable to @media.

So, if these properties are settable, how do we distinguish the non-settable ones? The ol’ double-dash?

@env --foo 0px;

body {
  margin: env(--foo);
  padding: env(safe-area-inset);
}

That makes me wonder. If env() is no longer the distinguishing quality of being un-settable, then could we modify var() to support the same functionality?

@var --foo 0px;

body {
  margin: var(--foo);
  padding: var(safe-area-inset);
}

And if that is possible, would we need @var at that point? Or would @var be needed to expose variables to @media?

Then, if these properties are assignable and also available to @media, might they create recursion?

@env --foo 0px;

@media( width > env(--foo) ) {
  @env --foo 200vw;
}

EDITED: That last example, which I accidentally left un-finished.

othermaciej commented 7 years ago

I notice the resolution didn't include the proposed multi-value env(safe-area-inset), is that intentional or just wasn't considered at the time?

jakearchibald commented 7 years ago

@othermaciej https://github.com/w3c/csswg-drafts/pull/1819#issuecomment-329926347 – I agree with @grorg here. Although it works well for simple examples, it isn't as useful as I initially thought.

I guess we just have to watch out for anyone advocating patterns that would break on rotation, or future devices.

CadenP commented 7 years ago

Perhaps it's too late, but I had a thought regarding the name choice. What about val()? It's close enough to var() that it's familiar, and has the impression that it's unsettable, and would therefore be set by some other process (the browser or what have you). It's also short.

My only issue with it would be that it is almost too similar to var(), and might be mistakenly interchanged, though I am not convinced that this would be a major problem.

othermaciej commented 7 years ago

We're locked and loaded to ship env(), and there was a WG resolution on that, so we'd really prefer that it not change again.

CadenP commented 7 years ago

Ah, very good. Then don't mind me!

jkuss commented 6 years ago

Can anyone give me guidance on how to get at these new constant/env() properties from javascript?

kiding commented 6 years ago

@jkuss This is actually quite interesting. I believe at this moment WebKit, being the only User Agent shipped with this functionality, offers no JS API to directly obtain envs in general. Interestingly, WebKit has listed some of the name strings (i.e., safe-area-inset-bottom) in CSSStyleDeclaration object at [340] ~ [343], but the values are inaccessible through .getPropertyValue function unlike any other CSS property.

Being UA-specific things, it sort of makes sense that envs belong to the unholy navigator object. But on the other hand, I could see them being an another set of custom properties; defined by the UA not by users, so there is no reason to treat them differently from vars.

I know this problem is right in the middle of a standardization minefield, and probably out-of-scope of CSSWG, but it doesn’t particularly seem in-scope anywhere else.

EDIT: Issue 1 of the new spec draft suggests an API should be defined on Document.

js-choi commented 6 years ago

Safari Technology Preview 49 has added support for parsing calc() in media queries, which may be a prelude to supporting env() in MQs as well.

kiding commented 6 years ago

Safari Technology Preview 52 has added two new ones: fullscreen-inset-top,fullscreen-auto-hide-delay.

zhouqicf commented 6 years ago

@kiding any documents about fullscreen-inset-top and fullscreen-auto-hide-delay?

kiding commented 6 years ago

@zhouqicf I don't think so, but you can infer them from WebKit's bug tracker.

tabatkins commented 6 years ago

Since Chrome wants to start working on env(), but no further action has happened in the PR, I've gone ahead and written up a draft spec for the feature: https://drafts.csswg.org/css-env-1/

There are a lot of open issues that need to be addressed. It would be a great help if @grorg could take a crack at answering them here; I'll be happy to edit them into the draft. ^_^

jonjohnjohnson commented 6 years ago

Per... https://drafts.csswg.org/mediaqueries/#units

Relative length units in media queries are based on the initial value, which means that units are never based on results of declarations. For example, in HTML, the em unit is relative to the initial value of font-size, defined by the user agent or the user’s preferences, not any styling on the page.

Any idea on how "user’s preferences" will be interpreted, allowing media queries use of something like calc(env(user-font-size) * 20)?

Also, this reminds me of discussion currently happening in #2430 - font-size: 'medium' value is the user's preferred font size.

MatsPalmgren commented 6 years ago

tho user agents may define additional undocumented environment variables

I think this will lead to vendor-specific, or even device-specific, dialects of CSS. That seems like a very bad idea to me.

js-choi commented 6 years ago

@tabatkins’ new draft specification mentions:

Because environment variables don’t depend on the value of anything drawn from a particular element, they can be used in places where there is no obvious element to draw from, such as in @media rules, where the var() function would not be valid.

I want to express that I am glad that this is still being included. There appears to be at least some community demand for this use case, as evidenced by this Stack Overflow question: “CSS native variables not working in media queries”. I myself would find them quite useful as an author.

AndreasGalster commented 5 years ago

Are there still plans to advance the spec, make envs definable by users and usable in media queries?

upsuper commented 5 years ago

Are there still plans to advance the spec, make envs definable by users and usable in media queries?

Custom envs can probably be discussed in #2627, and for that to be used in media queries probably #3578.

hober commented 3 years ago

Given that css-env-1 exists now, can this issue be closed?