[csswg-drafts] [css-properties-values-api] Extend `@property` at-rule to support globally settable values (#7866)

brandonmcconnell has just created a new issue for https://github.com/w3c/csswg-drafts:

== [css-properties-values-api] Extend `@property` at-rule to support globally settable values ==
# 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:

```css
:root { /* truly global variables */ }
 html { /*   global-ish variables */ }
 body { /*  body-global variables */ }
```

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:
  ```
  html ━┓
        ┣━ head
        ┗━ body ━┓
                 ┣━ [input="checkbox"]#darkmode
                 ┗━ main#page
  ```

  ```css
  #page {
    --color-bg: white; /* ⬜️ */
    --color-text: black; /* ⬛️ */
  }
  #darkmode:checked + #page {
    --color-bg: black; /* ⬛️ */
    --color-text: white; /* ⬜️ */
  }
  #page {
    background-color: var(--color-bg); /* ⬛️ */
    color: var(--color-text); /* ⬜️ */
  }
  ```

  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
> TL;DR: Extend `@property` at-rule to support a `global` property that allows  variables' values to be globally settable, so they only ever contain one value that can be referenced from anywhere, that with the greatest specificity

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):

```
html ━┓
      ┣━ head
      ┗━ body ━┓
               ┣━ header ━┓
               ┃          ┣━ img#logo
               ┃          ┣━ nav ━┓
               ┃          ┃       ┗━ ul ━┓
               ┃          ┃              ┣━ li.menu-item
               ┃          ┃              ┗━ li.menu-item
               ┃          ┗━ [input="checkbox"]#darkmode
               ┗━ main#page
```

```css
@property --color-bg {
  syntax: "<color>";
  inherits: true;
  initial-value: white; /* ⬜️ */
  global: true;
}
@property --color-text {
  syntax: "<color>";
  inherits: true;
  initial-value: black; /* ⬛️ */
  global: true;
}
#darkmode:checked {
  --color-bg: black; /* ⬛️ */
  --color-text: white; /* ⬜️ */
}
#page {
  background-color: var(--color-bg); /* ⬛️ */
  color: var(--color-text); /* ⬜️ */
}
```

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):

```css
@property --color-bg {
  syntax: "<color>";
  inherits: true;
  initial-value: white; /* ⬜️ */
  global: true;
}
@property --color-text {
  syntax: "<color>";
  inherits: true;
  initial-value: black; /* ⬛️ */
  global: true;
}
/* using :where() for 0 specificity */
:where(#darkmode:checked) {
  --color-bg: black; /* ⬛️ */
  --color-text: white; /* ⬜️ */
  background-color: var(--color-bg); /* <-- 🟥 red, set via the style below */
}
#page {
  background-color: var(--color-bg); /* 🟥 */
  color: var(--color-text); /* ⬜️ */
  --color-bg: red; /* <-- 🟥 this would be used (greater specificity) */
}
```

In the above example, even under `:where(#darkmode:checked)`, where the value for `--color-bg` is explicitly set to `black`, it would still evaluate to `red` since that is the value with the greatest specificity, as declared in the style below it (under `#page`).

# Syntax
```css
@property --property-name {
  /* other @property properties */
  [global?: boolean];
}
```

The global property would be optional, `false` by default.

Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/7866 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Tuesday, 11 October 2022 16:18:02 UTC