Open mirisuzanne opened 1 year ago
Possibly related #7523
Yeah, I'd rather solve custom at-rules this way than adding @custom-whatever
for everything with a prelude.
@global "<color>" no-inherit { --brand-color: teal; }
If this does exactly the same as @property
, then we should try to make it a special form of @property
intead:
@property "<color>" no-inherit { --brand-color: teal; }
That should be doable as long as the new prelude doesn't start with --
.
Currently, the rules for property registration severely limit the initial value of any property with non-universal syntax – requiring them to be 'computationally independent'. For example, a property with <length>
syntax cannot have an initial value of 3em
. Even without a syntax, variable cross-reference is not allowed
I don't know the original purpose of that restriction, but it would be a potential blocker here. The goal would be for authors to use this in place of setting all custom properties on root. But many custom property use-cases rely on relative units and relationships between variables. If those things aren't allowed in the registration syntax, then authors will still be forced to define custom properties in two places.
This issue is doing two separate things: mass registrations and making things "global". We should keep those topics separate; mass registration is already in #7523. (And I've just dropped a syntax suggestion over there that I quite like, modulo the rule name.)
For "global" things, there are two possible behaviors you could be invoking:
Calling back to my env() comment, I think we can cleanly achieve 1 without even having to do anything by just letting env()
take a dashed-ident. (Well, technically it already does allow that, but no dashed-ident keywords are defined for it or ever will be.) If the "environment variable name" is dashed, it just looks up the initial value of the corresponding custom property, done.
For 2, this could just be a new descriptor in the registration block. Dunno what a good name is, tho.
Yeah, I'd rather solve custom at-rules this way than adding @custom-whatever for everything with a prelude.
I'm not sure what you meant by this, @andruud.
For "global" things, there are two possible behaviors you could be invoking
I was in fact invoking behavior 1, and mentioned env()
as an option. Glad to hear that could work reasonably well.
I'm open to closing this as a duplicate of:
But the reason I opened this issue in the first place is to capture where they overlap in terms of author use-case and needs. Because they do. Currently @property
is not a reasonable solution for defining either local or global variables, and authors would like a solution that can handle both in one place. So our solutions have to work together in an elegant way here. Because, from an authoring perspective, they are part of the same project of defining the initial/global state of variables.
For example, the limitation I mentioned above is a likely issue for both proposals. Initial values restricted in that way will not cover the use-case for global values, and will also not act as a replacement for defining custom properties on the root element.
(I linked @custom-media
above, and mentioned global variables could be used to achieve the same thing. I believe Anders was referring to that.)
For example, a property with
syntax cannot have an initial value of 3em.
I think we probably already rely on initial values being in absolute/cononical-unit form, but assuming it's possible, it could be very bad for performance. In the worst case, it will be as if you specify all your "global" custom properties with * { ... }
instead of :root { ... }
. But I definitely see your point.
Even without a syntax, variable cross-reference is not allowed
But maybe we can allow env()
?
If the "environment variable name" is dashed, it just looks up the initial value of the corresponding custom property, done.
That actually sounds very nice? And using env()
makes it clear that it's a not an element-specific lookup.
I'm not sure what you meant by this, @andruud.
(I linked @custom-media above, and mentioned global variables could be used to achieve the same thing. I believe Anders was referring to that.)
Yeah, I meant that if you can substitute arbitrary stuff into @media (...)
with env()
, then probably @custom-media
isn't needed. Nor is @custom-container
, nor @custom-future-thing
.
Yeah, I meant that if you can substitute arbitrary stuff into
@media
(...) with env(), then probably@custom-media
isn't needed. Nor is@custom-container
, nor@custom-future-thing
The current @custom-media
is defined in such a way that they are boolean features when used. It is not a token or component value substitution.
@custom-media --foo only screen and (min-width: 300px);
@media (prefers-color-scheme: dark) and (--foo) {}
Can the same be achieved with env()
?
CSS authors really like being able to write @media (--tablet) {}
with tooling.
@media (min-width: env(--tablet-width))
won't be as handy.
There's a few variable management issues getting attacked here! Love to see it 😍
from https://github.com/w3c/csswg-drafts/issues/9206#issuecomment-1683089672
If the "environment variable name" is dashed, it just looks up the initial value of the corresponding custom property, done.
env(--screen-sm)
, usable in places where var()
is not, like @custom-media
, @media
, @container
, etc? lgtm 👍🏻
from https://github.com/w3c/csswg-drafts/issues/9206#issuecomment-1683089672
The property should be static ... this could just be a new descriptor in the registration block. Dunno what a good name is, tho.
what about immutable
?
syntax based on https://github.com/w3c/csswg-drafts/issues/7523#issuecomment-1683831209
@property {
syntax: "<length>";
inherits: false;
immutable: true;
initial-value: 2147483647px;
--width: initial; /* 2147483647px */
--height: initial; /* 2147483647px */
--depth: 0px;
}
That covers:
var()
is not. this is basically env()
adding --
supportMissed?
:root
authors would like a solution that can handle both in one place
from https://github.com/w3c/csswg-drafts/issues/9206#issuecomment-1683125310Seems like we still need ::document
, @document
, @global
global space. Local vars being global isnt solved with the @property
bulk syntax or env()
vars.
immutable: false
is a double negative that should probably be mutable: true
instead.
But I think I prefer global
, to make it clearer that these will be globally available.
It can also make sense adding the other option described by Tab: static props accross the tree. I can imagine wanting to change some color props depending on a media query checking prefers-color-scheme
, so the props can't be used in media query conditions, but wanting to avoid perf problems if there are lots of these props.
I agree with @tabatkins that mass registration and global variables are separate issues.
It would be good to support a way of declaring global declarations that doesn't just center around custom properties, to address the needs for things like ::document
and friends that keep popping up (here's one from just the other day)
An important requirement for these globals is that they need to be able to express values that depend on other globals, otherwise their utility for things like design systems will be limited.
For example, consider:
--color-magenta-h: 335;
--color-magenta-s: 90%;
--color-magenta-l: 50%;
--color-magenta: hsl(var(--color-magenta-h) var(--color-magenta-s) var(--color-magenta-l));
--color-accent: var(--color-magenta);
--color-accent-lighter: hsl(from var(--color-accent) h s calc(l * 1.2));
/* etc etc */
(sure, wider adoption of CSS Color 5 will reduce the need to specify components separately, but the rest still applies...)
Would the env()
solution support that?
I suppose it could be defined as basically "var()
that only draws from properties defined within a ::global
rule" (or whatever we call it).
Yes, that's why I think the issues are related. From an authoring perspective, the way design tokens are often managed, I want that same ability to create related tokens in both local var()
and global env()
values – and often have them match. I'm proposing exactly the logic above: "var()
that only draws from properties defined within a ::global
rule".
Design tokens in practice do not fall into neat 'global-and-independent' vs 'local-and-relational' categories. If we are providing both a way to make tokens globally available, and a way to define tokens in bulk, those two features are fundamentally part of a the same author use-cases. We can certainly implement them separately, but they have to play well together as part of a shared design-token-management work flow.
@mirisuzanne so to make sure I understand: env(<custom-ident>)
would resolve based on the regular custom properties defined ONLY in ::global
rules? And these regular custom properties would be usable via var()
as well, with ::global
sitting above :root
in the inheritance chain?
@LeaVerou Yes, that's what I had in mind.
I think that would be great, if it's implementable.
@mirisuzanne so to make sure I understand:
env(<custom-ident>)
would resolve based on the regular custom properties defined ONLY in::global
rules? And these regular custom properties would be usable viavar()
as well, with::global
sitting above:root
in the inheritance chain?
To clarify this a bit more: I would have env(<custom-ident>)
resolve based on the regular custom properties as defined in the global rule only. Env would always return the global value of the property. But I would not have a restriction that those custom properties remain constant throughout the cascade. Custom properties could get different contextual values accessible to var()
, and that would have no impact on their global value or access via env()
.
The handful of issues have gotten entirely mixed up and cross-talking, so I'll just link to my comment in #7523 about why it's still important to have immutability as a (perf-oriented) feature.
As an author, I would really-really want almost everything proposed in this issue and the comments below.
In 2020 I did propose to make some of the fields in @property
optional (https://github.com/w3c/css-houdini-drafts/issues/994), but my attempt was not successful at that time.
Maybe this issue could lead to us having a better way to define both custom properties, and global immutable tokens in a much more convenient way.
One thing I want to add/ask: does someone know of any benchmarks/research on how the inheritance of the custom properties impacts performance?
What could be potential gains from a situation where we, let's say, convert a thousand of design-tokens from a :root
defined just as --foo
, this being inheritable, to immutable global env variables, from a browser engine standpoint? Probably could be tested right now with the @property
, but we don't have any good tools in the current browser developer tools to measure this outside of the “black box” that is currently all the style recalculation/layout boxes in the performance flamegraphs.
Quick question. What about non global constants?
There has been discussion in several issues recently about providing a
::document
selector, or@document
/@global
/@env
rule that would allow authors a quick way to register globally available constants (as custom properties or environment variables) - without a full@property
rule describing each individually. See, for example:(Hopefully this issue is useful as a way of combining several discussions, and not just a duplicate)
Those first two issues have more specific concerns that could be (or have been) addressed in specific ways - but mention the possibility we might still want a more generic solution for registering global properties. Custom media queries (
@custom-media
) have similarly been solved as a specific case, but not yet implemented – and there's an open issue for adding@custom-container
. Those use-cases could also be solved by a well-defined global parameter registry.There are two primary overlapping concerns here:
@property
rule is a very bulky way to register them one-at-a-time.var()
would not be possible to resolve.I tend to agree with @tabatkins that:
I like the proposed solution of allowing global reference to the initial values of registered custom properties, but think we need to make that registration simpler for it to work. I don't know the right syntax for that, but if we're able to come up with something compact, it could also be useful for defining parameters in declarative custom functions and (someday, maybe) mixins.
The
@property
rule can register a custom propertyname
with three associated descriptors:syntax
: the CSS type/grammar(s) used to validate and interpolate values (often but not always a single type like "<color>
" or "<length>
"'inherits
: a boolean to set if the property inherits or not (this would not be needed in function/mixin parameters)initial-value
: the initial value of the propertyAuthors often skip this registration for most properties, and only define the
name
along with an un-registered not-technically-'initial' value on the root element. Then some authors add the@property
registration for specific properties - often to define asyntax
for the sake of interpolation, but occasionally to set inheritance or a more formal initial value.Things I would expect from a syntax:
syntax
The main complexity that I see with defining a compact syntax is that custom property values (including initial values) are very permissive. It will be difficult to combine
initial-value
with anything else on the right side of a standardproperty:value;
format – though it should be possible using either the!
delim-token, or wrapping()
/{}
/[]
of some kind. The other thought I had was to put some of the definition on the left side. Some rough ideas:The an all-in-one syntax is more appropriate for reuse in function parameter definitions, while the grouped syntax helps with defining a whole set of related variables in a single place.
On the other end, when calling custom properties, a
env()
orglobal()
function could give access to the initial registered value anywhere in the document, whilevar()
returns only the cascaded value. I prefer this to a context-dependent resolution ofvar()
, since it provides more clarity on what value to expect, and makes both available wherevar()
is already supported:This is far from a complete proposal, and mostly a request that we consider these use-cases together. Happy for thoughts, and willing to merge this into the
env()
conversation if it belongs there.