-
Notifications
You must be signed in to change notification settings - Fork 711
[css-properties-values-api] add support for scope-global variables #7866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think you can do this today with /* using :where() for 0 specificity */
:root:has(:where(#darkmode:checked)) {
--color-bg: black; /* ⬛️ */
--color-text: white; /* ⬜️ */
}
/* using :where() for 0 specificity */
:where(#darkmode:checked) {
background-color: var(--color-bg); /* <-- 🟥 red, set via the style below */
}
:root:has(#page) {
--color-bg: red; /* <-- 🟥 this would be used (greater specificity) */
}
#page {
background-color: var(--color-bg); /* 🟥 */
color: var(--color-text); /* ⬜️ */
} I am unsure if custom key/values with Keeping track of which custom props are global or not will be tricky. |
@romainmenke Thanks for your feedback! I appreciate it!
It does, however, get a bit trickier once you start storing complex or dynamic values. Consider the below example:
@property --range-min {
syntax: "<number>";
inherits: true;
initial-value: 0;
global: true;
}
@property --range-max {
syntax: "<number>";
inherits: true;
initial-value: 0;
global: true;
}
span::before {
content: "In the form below, select a value between " var(--range-min) " and " var(--range-max);
}
form > input[type="range"]#range {
--range-min: attr(min number);
--range-max: attr(max number);
} Something more dynamic like this would not be dynamically possible using only This is just one more example, though I think we could probably come up with many more where this would enable developers to perform more powerful operations using CSS. Good thinking on Personally, I think I still lean toward |
Another idea to help differentiate globally set variables from locally set variables would be to implement a unique syntax unique to when you want to set a global/scoped variable. Even more powerful, I think, would be to enable variables to be used both ways, locally and globally (within the current scope), by using or not using this syntax. There's already quite a bit of discussion going on surrounding the meaning and implications of One caveat I can foresee to my spec here already is that if a third party uses the same variable names you do with a global scope, there may be a conflict where their values override yours and vice-versa. Alternatively and more safely, I propose making these "global" variables scoped variables instead of making them truly scoped, which feels a lot less dangerous to me already. They could be used the same way variables are already used, with or without
I'm considering this new syntax for a couple of reasons—
That said, I think Here's an example of how this change could look in practice: main {
span {
--some-prop-1: var(--title); /* this will NOT work, as no value for `var(--title)` exists to be naturally/locally inherited within the current cascade */
--some-prop-2: var(&--title); /* this WILL work, uses value from current scope, declared LATER in `main + div[title]` */
}
& + div[title] {
&--title: attr(title);
--addtl-title-1: var(--title); /* same as `var(&--title)`, defining globally also defines locally */
--addtl-title-2: var(&--title); /* global works in the same selector here as well */
div {
--content-1: var(--title); /* same value as parent's `title` attribute, naturally/locally inherited */
--content-2: var(&--title); /* parent's `title` attribute, but references scoped variable, not inherited */
--title: "Test";
&::before {
--content-1: var(--title); /* "Test", inherited from parent, locally */
--content-2: var(&--title); /* grandparent's `title` attribute, references scoped variable, not inherited */
/* var(--content-1) ≠ var(--content-2) */
}
}
& + span {
--some-prop-1: var(--title); /* this will NOT work, as no value for `var(--title)` exists to be naturally/locally inherited within the current cascade, even though the previous sibling holds a value */
--some-prop-2: var(&--title); /* this WILL work, uses value from current scope, declared LATER in `main + div[title]` */
}
}
} If, however, it would in fact be more confusing to the compiler to use element {
*--some-value: 5;
--other-value: var(*--some-value);
} |
@property
at-rule to support globally settable values
Circling back to this as it's been a while since the last activity. Are there any steps I can take to push this proposal forward, along with its related dependent #7869? attn @romainmenke cc @tabatkins |
The problem
In many cases, a developer may want to set a variable value that can be accessed and re-used later in their application. CSS custom properties (variables) already support inheritance with or without using
@property
, so if I set a variable value, I can—in the same or a descendant element—reference that variable to use its value. This is helpful, but it also requires that all global variables be set in a high enough scope to be exposed to the entire document, such as one of these:The issue here is that sometimes, we encounter situations where we want to dynamically set one of those global values based on a set of c conditions related to the state of the DOM. If we combine our needs for DOM-aware variables and global-ish variables, we only have two options—
Utilizing JS
The most flexible solution to this problem is to fall back to JS and set up whatever observers or event listeners we need to continually write and rewrite the value of our variables when relevant DOM changes occur. This option is workable but blurs the line between JS and CSS in an area that I think CSS should be able to handle without using JS as a crutch
CSS-only
A way to accomplish DOM-aware global-ish variables purely in CSS would be to set up options or toggles high up in the DOM/cascade and then let those values trickle down. This is efficient from a CSS-only perspective, but it strictly enforces how we add some global values, and therefore, our pattern of development when working with CSS, employing a non-negotiable and inflexible method for setting up our global values that does not leave much room for designing UX-first or including such toggles in a way that naturally flows with the order of the page.
A simple example of this might be a checkbox at the top of the DOM adjacent to a container that toggles between light and dark mode styles based on the
:checked
state of the checkbox (assuming the checkbox is checked):It might look like this:
This may be doable for one or two toggles, though hardly ideal. As more options/toggles are added, such an implementation becomes more difficult to maintain, not to mention that the actual problem and use cases extend far beyond simple toggles. Pretty soon, you're forced to organize a handful of toggles either at the top of your document or in an absolute or fixed space so that you can manage them> Even then, without using JS, you can't even collapse them to save space, as containing them would break your ability to reference them using the sibling selectors (
+
or~
), so in most even remotely sophisticated examples, developers are forced to employ JS to manage styles like these.Description of the proposal
I propose adding a single boolean
global
property to the@property
at-rule, which would expose those variables in a different way, where instead of the variables taking on different values in various places and those values flowing down the cascade, the value of the variable would always match the most specific value set anywhere within the CSS.With this in place, such a variable's value could be set anywhere in the cascade/DOM, and only the value with the greatest specificity would be stored, which would be the value reflected everywhere that variable is referenced within a property value.
For added context, before settling on this proposal spec, I ran through several alternative ideas—even one that added functions that would reference elements inline by selector/id and could be used anywhere—but this is the first which felt as though it genuinely worked with the existing nature of CSS and added value rather than fighting against the "CSS way" simply to meet a need.
Examples
Using the same example from above, something like this would now be possible (assuming the checkbox is checked):
The beauty of this solution, in my opinion, is that it still utilizes the same specificity rules as CSS, except that instead of specificity being evaluated per element/selector when a style is applied, that value with the greatest specificity is applied to the variable itself at the global scope.
For example, if a value of greater specificity were declared further down in the scope, that would be the value used constantly, so long as it remains the greatest specificity, as seen in this example (using the same DOM structure as above, assuming the checkbox is checked):
In the above example, even under
:where(#darkmode:checked)
, where the value for--color-bg
is explicitly set toblack
, it would still evaluate tored
since that is the value with the greatest specificity, as declared in the style below it (under#page
).Syntax
The global property would be optional,
false
by default.The text was updated successfully, but these errors were encountered: