Multiple defaults for CSS Custom Properties
bennypowers opened this issue ยท 15 comments
Consider this case
class MyElement extends HTMLElement {
constructor() {
super().attachShadow({ mode: 'open' }).innerHTML = `
<style>
:host { color: var(--my-color, red); }
@media (prefers-color-scheme: dark) {
:host { color: var(--my-color, blue); }
}
</style>
`;
}
}
Should I get
"cssProperties": [
{
"name": "--my-color",
"default": "red"
}
]
Or
"cssProperties": [
{
"name": "--my-color",
"default": "blue"
}
]
And if I get either, that's not really correct is it?
We should add a schema facility for declaring multiple defaults and their conditions
Should we consider fallback values to be "default"? I would expect a default value to be something that is explicitly set:
:host {
--my-color: red;
}
That being said I suppose you could have the same problem here:
:host {
--my-color: red;
color: var(--my-color);
}
@media (prefers-color-scheme: dark) {
:host {
--my-color: blue;
}
}
One might argue that red
is the default here given that blue
is only set conditionally, yet red
is always set.
the spec language refers to those as defaults
it's an interesting point you make, but I'm more concerned here with the 'multiple' part than the 'default' part
:host { --my-color: red; color: var(--my-color); }
that will prevent users to providing --my-color
as the host values set in the web components will always take prio over a user provided value.
So it's actually not a "valid" example I would say
the spec language refers to those as
defaults
it's an interesting point you make, but I'm more concerned here with the 'multiple' part than the 'default' part
Oh interesting! I did not know it considered those defaults. Good to know ๐ .
that will prevent users to providing --my-color as the host values set in the web components will always take prio over a user provided value
Not sure how the spec defines this, but in practice my example does work and users can override the --my-color
var: https://codepen.io/liamdebeasi/pen/wvdgvJE
I can't think of a syntax that would elegantly express all of the possible defaults and what criteria makes each value become active.
It might be useful to wait and see how developers are using CEM first before making a decision on this. Right now I think this feature would requires us to make assumptions about how developers are structuring their CSS, and I am not sure we have all the required information to make an informed decision on this.
For additional context, Stencil does not list the default values for CSS Variables in its docs output: https://github.com/ionic-team/stencil/blob/45388e95edb46ef357eb9ae37cd32bbb5bc1ed23/src/declarations/stencil-public-docs.ts#L101-L105.
The open-wc analyzer, for it's part, does accept a default value in @cssproperty [--my-prop=red]
I guess the thing that trips me up here is that I have always considered a "default" value to be a single value that is explicitly set (such as --my-color: red
), though in something like Ionic Framework this is not always the case when it comes to theming.
Ionic Framework applies themes based upon light vs. dark mode as well as platform mode ('ios'
vs 'md'
), so there are different ways of determining the default value. We usually stay away from stating the default value for a CSS variable in the docs because the answer is typically "It depends!" ๐ .
I would be interested to see if others have successfully documented variables with multiple defaults.
Edit: Included what it would look to take example code and include within the manifest.
Hey there! Long time listener, first time caller.
I do certainly see value in adding a "conditional" concept on certain marked up CSS variables. Something that could take in something to the effect of @prop {conditional} --background: Background of the alert
and transforms it. The conditional could be an arbitrary string (Dark Mode
), media query (@media (prefers-color-scheme: dark)
), or a selector (html[mode="md"]
).
With the example:
:host {
/**
* @prop() --my-color: Change the color of the button
*/
color: var(--my-color, red);
}
@media (prefers-color-scheme: dark) {
:host {
/**
* @prop() {@media (prefers-color-scheme: dark)} --my-color: Change the color of the button
*/
color: var(--my-color, blue);
}
}
If a processor wants to automatically infer based on the wrapped media query, they can do so.
After processing, the manifest could then look like:
"cssProperties": [
{
"name": "--my-color",
"description": "Change the color of the button",
"default": "blue",
},
{
"name": "--my-color",
"description": "Change the color of the button",
"default": "red",
"condition": "@media (prefers-color-scheme: dark)"
}
]
Risk here is bloat to the file afaik. But I can see some helpful documentation coming out around this feature.
But what would it look like in the manifest?
Updated my comment above to be a little more cohesive
Not sure how the spec defines this, but in practice my example does work and users can override the
--my-color
var: https://codepen.io/liamdebeasi/pen/wvdgvJE
interesting - it behaves differently when using constructible stylesheets ๐
e.g. with inline styles a selector on the main document like
html {
--my-color: blue;
}
still applies.
When using using constructible stylesheets then it won't ๐ค
I see these as variations or contexts. They're still defaults for the same CSS property but they are dependent upon certain conditions. What about defining these contexts in connection to the primary definition like this:
"cssProperties": [{
"name": "--my-color",
"description": "Change the color of the button",
"default": "blue",
"contexts": [{
"description": "Dark mode button color",
"default": "red",
"condition": "@media (prefers-color-scheme: dark)"
}]
}]
Something to consider: if condition
or conditions
is meant to be machine-readable, then the values should be strictly identical
i.e. a tool like storybook or <api-viewer>
might check a css property's contexts like so:
propertyContext.condition === '@media(prefers-color-scheme: dark)'
Or, it might perform a regexp check that allows for whichever whitespace rules that condition's language allows.
@bennypowers Do you envision condition being an object with pre-defined types perhaps? Conditions can be component or browser-centric.
Some possibilities:
- media query
- container query
- specific attribute present
tricky
on the one hand we'd prefer to keep our own enum right? makes it easier for consumers
but on the other hand, we'd have to anticipate every case and keep up with browser specs
My 'gut reaction' is to keep it open as a string condition and have tools fight it out. I think there's precedent for that in Type
in the schema, which doesn't assume anything
Another complication here is that @media
and @supports
queries can be nested indefinitely (I don't know if container queries can or not and Google didn't give me a quick answer). Here's an example from CSS Tricks:
@media (min-width: 2px) {
@media (min-width: 1px) {
@supports (--a: b) {
@supports (display: flex) {
body {
background: pink;
}
}
}
}
}
If a custom property was defined at some inner level I have no idea how you'd represent it in the manifest or in an e.g. @cssproperty
tag.