Skip to content
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

[css-env] Adding custom env() variables #2627

Open
jonathantneal opened this issue Apr 30, 2018 · 34 comments
Open

[css-env] Adding custom env() variables #2627

jonathantneal opened this issue Apr 30, 2018 · 34 comments

Comments

@jonathantneal
Copy link
Contributor

jonathantneal commented Apr 30, 2018

https://drafts.csswg.org/css-env-1/

What are the current ideas being pitched to add custom env() variables via CSS?

To add custom env() variables, could a similar syntax be used as seen in custom media and custom selectors?

@custom-env --some-width 30em;

Or what other issues might there be?


Should it work on a rule level, so you can insert arbitrary stuff into a rule, like reusing a block of declarations?

While I feel like the answer should be “not yet”, if the answer to this question is yes, could syntax like @media (curly braces) be used?

@custom-env --some-centering {
  align-items: center;
  justify-content: center;
}

As for JS, I hope that can be resolved separately via Houdini, because I’m under the impression there are still many things to work out with the similar concept of custom properties (as well as shorthands which apply to both, or selectors and rulesets that could apply to env()) .

@jonathantneal jonathantneal changed the title [css-env] custom env() variables Apr 30, 2018
@upsuper
Copy link
Member

upsuper commented May 7, 2018

I think the general idea is to allow script to set them. But it's probably a good idea to allow stylesheets specify default values to them. Probably something like

@env {
    --some-width: 30em;
    --some-color: #000;
}

works.

@tomhodgins
Copy link

What would the advantage of 'custom env() variables' be compared to using the CSS custom properties we already have?

These env() values could potentially be things the browser user has influenced (like if they have a setting for 'prefer low motion' or 'high contrast' that gets exposed as an env(), or information about the device, OS, and browser it's being displayed in) but if they're settable by CSS authors that seems like it defeats the purpose of them—if it's the case that we want custom settable variables and special variables with reserved names that the browser supplies to value to why not expose these values as some kind of reserved default CSS custom property, what would we even need env() for?

@tabatkins
Copy link
Member

env() is guaranteed global, which means you can store them in a much more memory-efficient whole-page data structure, rather than on each individual element like currently. (Chrome, at least, attempts to detect custom props that are set on the root element and not set anywhere else, and automatically upgrade them to such a data structure.)

env() also should be usable in further contexts outside of properties on elements, such as in MQs. This is fundamentally impossible with var().

@jonjohnjohnson
Copy link

jonjohnjohnson commented Nov 6, 2018

I hope this is the place for ideas to prevent use of author-defined env() causing FOUC.

In #3285 (comment) @tabatkins says...

Setting a new env() value would trigger a reparse in this idea.

Any thoughts on what @upsuper points out in #2627 (comment)?

@matthew-dean
Copy link

I hope the implementation of how users can define environment variables can happen at the same time or shortly after the implementation of referencing env variables.

@MappingSteve
Copy link

MappingSteve commented Oct 31, 2019

@tomhodgins re

What would the advantage of 'custom env() variables' be ...

media queries can't use var, because they are not attached to an element.

Use case: in css, define --minW: 400 px; Now wish to create a media query:
@media (max-width: 400 px) {}
Except instead of hard-coding 400 px, use var(--minW). Can't.

In my case, I have a calculated "base font size" that grows/shrinks based on window width, but stops at min/max values at specified min/max width. Yeah, accessibility issues if I do this wrong, so I may end up with javascript to resolve the various inputs to this calculation, but that isn't the point here. Unless max/min or if ... then ... else ... expression is added to css, this requires media queries at the min/max widths, to override the formula. Or must use a pre-processor. or javascript. Would be nice to express it fully with css. OTOH, TBD at what point it stops being worth burdening css parsers with additional functionality.

@tomhodgins
Copy link

I have a calculated "base font size" that grows/shrinks based on window width, but stops at min/max values at specified min/max width.

Sounds like a job for clamp(), rather than media queries:

:root {
  font-size: clamp(10px, 10vw, 50px);
}

As for using custom identifiers in Media Query conditions, though you do need to process it (either ahead of time, or at runtime) there's already flexibility in CSS to allow you to encode some nice breakpoints, and write at-rules that use those user-defined breakpoints, and interpret those at-rules as though they were media queries written for the values those custom breakpoints represent:

:root {
  --breakpoints: {
    "narrow": "(min-width: 300px)",
    "medium": "(min-width: 600px)",
    "wide": "(min-width: 900px)"
  }
}

@supports (--breakpoint("narrow")) {
  html {
    background: red;
  }
}

@supports (--breakpoint("medium")) {
  html {
    background: green;
  }
}

@supports (--breakpoint("wide")) {
  html {
    background: blue;
  }
}

Demo: https://codepen.io/tomhodgins/pen/yLBOyBe

So nothing else needs to be added to CSS to accomplish either the scalable font-size functionality where a scalable unit is capped with a minimum and maximum it won't go beyond, as well as the ability to encode custom breakpoint names and refer to them, though for the second one you'll need a little bit of code to help either in advance, or like my Codepen demo in the browser at runtime :D

@matthew-dean
Copy link

@tomhodgins I'm not sure why you seem to be arguing against user-defined environment variables? 🤔 There's no potential conflict if user-defined environment variables need to be prefixed with --, which system-level variables would not be, and as others have pointed out, not only are there performance benefits (they don't have to trickle down and be calculated through the cascade), but they can have use-cases that custom properties don't allow. I mean, it's cool that custom props can be hacked to do some cool things / be set via scripts, but that doesn't fundamentally change that they're inherently more expensive to calculate than environment vars, because they factor in the complexity of markup and related style calculation.

@tomhodgins
Copy link

I'm not sure why you seem to be arguing

Please don't think I'm arguing, nothing I'm saying here is meant like that at all! 😩

I'm showing some things the platform can do, no hostility intended.

@matthew-dean
Copy link

@tomhodgins Ah okay, fair enough. Sorry if I misunderstood your position!

@flavi1
Copy link

flavi1 commented May 6, 2020

Hello,

@tomhodgins:
I'm really interresting about your breakpoint solution. I tried your great codepen link. I also find the Houdini project with the JSON in CSS feature. Then, in this solution, we got two things.
1 - JSON in CSS with ability to access their first level property in this form : --breakpoint("narrow")
2 - @supports (min-width: 300px) inerpretation as a media query.

But...
1 - If JSON in CSS var level 1 access becomes a CSS spec, i will say Ok. But for now, it's not a CSS solution, but a JS hack.
2 - @supports's role is to test if some features are supported or not. Conceptually, it's not ilogic to consider that if window width is 200px, and html width is 100% of it, then min-width: 300px should return false. But it suppose to grow up the concept of "supported feature" in a way that will become confused. "Is supported to assign 300px to min-width css property" is not the same that "is html able to get 300px width", and should always return true if browser supports min-width CSS property written in pixels.

So
It's an elegant JS hack, and maybe i will use it (waiting for another solution). But it's not, and probably never will become, a valid CSS solution.


For variables media queries breakpoints, we can imagine two simple possibilities.
1 - media queries should tolerate css variables defined on ":root", or in another new specific selector to avoid confusion.
2 - media queries should works with env(), then we have to be able to define custom env(--constant-var).

I think the first solution may be reasonable (and then may needs another ticket if not already existing). But it's only my point of view.

@flavi1
Copy link

flavi1 commented May 6, 2020

Maybe something like this:

@constants {
    --my-unalterable-var: my_Value_That_Is_Selector_Context_Agnostic;
    --breakpoint-s: 640px;
    --body-bg: blue;
}

@media screen and (min-width: var(--breakpoint-s, 300px)) {   /* because constants are selector agnostic */
    body {
        --body-bg: red; /* no effect, because --body-bg is already a constant (then it's unalterable) */
        background-color: var(--body-bg, green); /* blue, because --body-bg is simply already defined */
    }
}

(if --breakpoint-s is not set as constant, then media query min-width value will be 300px)

I'm going to
1 - Search in tickets if something similar exists.
2 - Post a ticket if not.

@sneko
Copy link

sneko commented Aug 12, 2021

Hi,

It's a bit related: I would like to override official safe-area-inset-top variable. Is there a way to do so?

My case is simple: I found a bug about its usage inside a hybrid native application and I would like from my desktop to simulate env(safe-area-inset-top) having a value (not being 0px).

If not, I think it would be helpful to simulate some cases where nested blocks use it and make things going wrong 😄

Thank you,

@matthew-dean
Copy link

@sneko IMO maybe the best way to handle that is to have a syntax that allows you to pull in an env() constant and set it to a new, global variable that you can reference? I think overriding a value provided by the environment feels out of scope.

@Aarbel
Copy link

Aarbel commented Oct 18, 2021

@sneko have you found a way since August ?

Cf storybookjs/storybook#12852

@sneko
Copy link

sneko commented Oct 18, 2021

@Aarbel I didn't. As @matthew-dean mentioned, I think the best is to think about it as a variable when you begin a project (assigning the real safe-area-inset-top value just once). So when needed you can override it with default CSS variable manipulation 😃

@Aarbel
Copy link

Aarbel commented Oct 18, 2021

@sneko thanks a lot for your quite feedback ;)

Can you just give a little example ?

@sneko
Copy link

sneko commented Oct 18, 2021

I didn't make the modification yet (as a quick workaround I just replaced all values since it was scoped to only 1 repository) but I would think about defining something like:

:root {
  --safe-area-inset-top: env(safe-area-inset-top);
}

And when you want to use it:

.myclass {
    padding-top: var(--safe-area-inset-top);
}
@clarfonthey
Copy link

I think the general idea is to allow script to set them. But it's probably a good idea to allow stylesheets specify default values to them. Probably something like

@env {
    --some-width: 30em;
    --some-color: #000;
}

works.

This solution seems like the most obvious choice to me, especially considering how it very closely mimics an already-existing feature, @page.

@Aarbel
Copy link

Aarbel commented Sep 28, 2022

This solution seems like the most obvious choice to me, especially considering how it very closely mimics an already-existing feature, @page.

Being able to also set this css thru javascript would be the best

@Somnium7
Copy link

I am also interested in having custom environment variables, exactly for use in media queries (for breakpoints and similar), where we can't use regular variables. clamp and similar functions are not always enough, especially when layout is different.

Are there any plans to introduce this? Solution proposed earlier seems good, and shouldn't be too hard to implement.

I think the general idea is to allow script to set them. But it's probably a good idea to allow stylesheets specify default values to them. Probably something like

@env {
    --some-width: 30em;
    --some-color: #000;
}

works.

@bramus
Copy link
Contributor

bramus commented Feb 22, 2023

There’s some overlapping discussion that went on in #6641, suggesting @global, @document, @root, … to store these globals in.

@ghost
Copy link

ghost commented Feb 28, 2023

I would like to know if it would be possible to import constants in CSS. Would it be possible to import constants in CSS?

(you all are talking about an alternative to custom media and custom selectors, would it be possible with constants or env... be something imported? For example:)

filename: constants.css
Here we have an initial file with some css variables and some values.

@constants {
    --my-unalterable-var: my_Value_That_Is_Selector_Context_Agnostic;
    --breakpoint-s: 640px;
    --body-bg: blue;
}

filename: main.css
As we can see, I import the constants file into another file: "main.css".

@import "constants.css" constants, breakpoint-s, body-bg;
@media screen and (min-width: var(--breakpoint-s, 300px)) {   /* because constants are selector agnostic */
    body {
        --body-bg: red; /* no effect, because --body-bg is already a constant (then it's unalterable) */
        background-color: var(--body-bg, green); /* blue, because --body-bg is simply already defined */
    }
}

(I find this proposal very interesting, technologies like javascript, typescript have a feature similar to this. But I don't know if you all know, but I think it would be really cool to import constants in a CSS code snippet)

filename: constants.js
In javascript we have something like export to allow the import of const type variables.

export const bodyBg = 'blue'; // 'green'
export const breakpoints = '640px'; // 'green'

filename: main.js
Whenever we create a file, we can import it with the name of the variables of the const type we must import.

import { bodyBg, breakpoints } from "/constants.js";
console.log(bodyBg); // blue
console.log(breakpoints); // 640px

Note: (Something I noticed when importing variables of type const in javascript is that variables of type const in javascript cannot change their value or be overwritten. I believe this is due to the fact that they are block-scoped. I think this should change in css, as variables in css can change their value and it wouldn't be const type.)

But my doubt would be if it would be possible to import css variables also inside the import - I don't know if the variables in css are of type const in "block-scoped" in js or are global like "var" as js too.

@bramus
Copy link
Contributor

bramus commented Feb 28, 2023

My concern with this proposal is that the syntax of constants clashes with that of custom properties. I think they should be obviously distinguishable from each other, as examples like these could be really confusing.

@media screen and (min-width: var(--breakpoint-s, 300px)) {   /* because constants are selector agnostic */
    body {
        --body-bg: red; /* no effect, because --body-bg is already a constant (then it's unalterable) */
        background-color: var(--body-bg, green); /* blue, because --body-bg is simply already defined */
    }
}

Here, an author would have to go through the entire stylesheet and imports to see why --body-bg isn’t red. If constants had a different syntax, it would be clear by just looking at it.

@ghost
Copy link

ghost commented Feb 28, 2023

Here, an author would have to go through the entire stylesheet and imports to see why --body-bg isn’t red. If constants had a different syntax, it would be clear by just looking at it.

Looking at your feedback and comment, from what I said earlier it would make sense then to assume that this custom env() is then a "var" (since it can change the value) and not a "const" (because cannot change the value)

My concern is similar to yours(this proposal is that the syntax of constants clashes with that of custom properties).

Another concern I have is how to import this, and whether or not there will be import like js does.

One of my concerns is that import doesn't allow special symbols in variables. Example:

filename: main.css

@import "constants.css" constants, breakpoint-s, body-bg; 

/* error  breakpoint-s, body-bg 
symbol not allowed for import - 
*/

@media screen and (min-width: var(--breakpoint-s, 300px)) {   /* because constants are selector agnostic */
    body {
        --body-bg: red; /* no effect, because --body-bg is already a constant (then it's unalterable) */
        background-color: var(--body-bg, green); /* blue, because --body-bg is simply already defined */
    }
}

vs

filename: /constants.js

export const body-bg = 'blue'; // 'green'
export const breakpoint-s = '640px'; // 'green'

filename: /main.js

import { body-bg, breakpoint-s } from "/constants.js"; 

/* error  breakpoint-s, body-bg 
symbol not allowed for import - 
*/

console.log(body-bg); // blue // error import
console.log(breakpoint-s); // 640px // error import

For me it wouldn't make sense to create custom variables if I can't reuse them in imports. As I said above, if this is used as an import, would it make sense to allow names with special characters?

@romainmenke
Copy link
Member

romainmenke commented Feb 28, 2023

For me it wouldn't make sense to create custom variables if I can't reuse them in imports. As I said above, if this is used as an import, would it make sense to allow names with special characters?

This sounds to me that you want to have this :

  • one abstract definition of "values" (broadest definition of value)
  • use these in JavaScript (client and/or server side)
  • use these anywhere in CSS

Which sounds more like what design tokens are trying to solve?
A single definition file that can be "imported" in various contexts.

@clarfonthey
Copy link

Worth pointing out that the distinction between var and env is already decided, and that the only question is on how defining env properties should work, rather than allowing vars in env contexts.

This is also why @env personally makes the most sense to me as a syntax, rather than @constants or something else -- it makes it clear that we're defining envs instead of vars. If we relied on :root, it would confusingly define both an env and a var, and then people would start to conflate the two.

@matthew-dean
Copy link

@ghost I think you make a good point that environment variables may be a special case where the referencing in CSS should be allowed in camelCase or dash-case (to point to the same var / value), and similar to the API for style properties, would be presented to JavaScript in camelCase even if they were defined as dash-case. This would allow the same interoperability that style values have now.

It's possible environment variables already have this in the spec, I don't know.

@matthew-dean
Copy link

matthew-dean commented Mar 16, 2023

@clarfonthey

This is also why @env personally makes the most sense to me as a syntax, rather than @constants or something else -- it makes it clear that we're defining envs instead of vars. If we relied on :root, it would confusingly define both an env and a var, and then people would start to conflate the two.

I think part of the issue @ghost is pointing out is that an env variable would theoretically be global for the environment (the window), which could lead you to the same types of problems when we used to do global vars in JavaScript, which is global pollution but also naming clashes. Probably an explicit import would be preferable, which maybe points to a 3rd type of variable, or a way to define importable local variables, similar to what @jonathantneal originally posted, but combined with the syntax that @ghost mentioned.

As in:

// vars.css
@var --breakpoint-s 320px;
// main.css
@import "vars.css" --breakpoint-s;
@media screen and (min-width: var(--breakpoint-s, 300px)) {
    body {
       background: red;
    }
}

It maybe has some confusion about where the value would be available, or which var could be used where, but other languages also have scoping rules for variable access. Otherwise there might have to be a third "variable" construct.

@clarfonthey
Copy link

I'm not 100% convinced that global variables are a problem here; JavaScript's global variables are a problem because they can clash with built-ins and other namespaces, whereas global environments in CSS would just be local to the styles, and nothing else. CSS has its own rules for handling conflicts, and I don't see a meaningful distinction between global environments and variables defined on :root besides the fact that the latter can be overridden by selectors. Defining something in two rules will still overwrite it, similar to how global variables work.

Like, I could see an argument in favour of allowing @media nesting, then allowing var inside nested @media queries to specifically refer to the variables for that element. But again, this affects specificity, and I think that adding environments would make sense for this purpose instead.

@matthew-dean
Copy link

whereas global environments in CSS would just be local to the styles, and nothing else. CSS has its own rules for handling conflicts

🤔 I guess, to be fair, custom properties defined in :root have the same possibility of clashing across CSS files from different sources, so yeah, you're probably right, and in this case, an @env block is probably fine.

@clarfonthey
Copy link

clarfonthey commented Mar 17, 2023

Mostly just to solidify the argument, we already have two things decided by the standard:

  1. Since CSS doesn't have the concept of namespaces, stylesheets have to handle name conflicts on their own. Yes, variables can be narrowed in scope to specific selectors, but if multiple different stylesheets included on a single page all define their own variables on the :root element, they have to handle name conflicts among themselves. This also applies to things like keyframes which have global names.
  2. The standard already makes the distinction between vars and environments, where vars are defined in selectors and only are usable in selector scopes, whereas environments are defined globally and are usable anywhere, including places that variables aren't allowed like in media queries.

You could argue that there are shortcomings of this approach, but that would require a completely separate proposal, rather than just one that lets you define environments, since the distinction between variables and environments already exists. Really, the only question is syntax, since CSS has a few standard things that we can work off of:

  1. In the same scope (and since environments are all global, they have the same scope), multiple definitions just override each other in the order they're found in the stylesheet. So, if someone defined an environment twice, it would just override the existing value.
  2. Variables use the -- prefix to define properties, meaning that all variables have the -- prefix. It would make sense for user-defined environments to have the same convention.

Additionally, although it's not strictly required, it makes the most logical sense to simply borrow the syntax for defining variables as properties and instead define a block syntax that makes these properties turn into environments instead of variables. So, it would make sense to have something like @env { --property: value } rather than @const --property: value, although both are technically possible proposals.

There could be a separate proposal to allow modules or namespaces which could later be expanded to include environments, but this wouldn't be part of environments themselves. It's a separate problem that people have tried to solve, but IMHO it's orthogonal to the concern of defining custom environments. Keyframes are a good example of this since these are also globally named.

@nasif-co
Copy link

This is a very interesting discussion! I believe being able to set our own environment variables that can be used inside MQ is very exciting. I find this to be especially powerful if we can override those values depending on the window. Perhaps one could use javascript to, for example, measure the width that the browser is giving all scrollbars and pass that to CSS as an environmental variable --scrollbar-width, which is updated on every window resize. Then we could do something like this:

@media ( env(--scrollbar-width): 15px ){
  ...
}

There are definitely other ways to do this that don't require MQs or environmental variables at all, easily being implemented with normal custom properties and calc(). However I do wonder if this could be a new and powerful way to pass data from js to css that affects the whole window and not specific elements. For example, an environmental variable that holds all current url params and allows us to set conditional styles depending of said params with MQs. Granted, could also be done with classes defined by js but its still food for thought.

I guess this dynamically changing environment variables should only be possible with js, since doing so from CSS could bring some infinite loop problems like for example:

@env {
  --my-env: 10px;
}

@media ( env(--my-env): 10px ){
  @env {
    --my-env: 5px;
  }
}

This is definitely something to think about. Maybe this is all out of the scope of css and actually better off being done another way from js, instead of trying to make MQs act like js if statements.


On another note, how would the syntax work for using these environment variables in media queries? Will I only be able to query for the exact value? Or would there be any ranges possible? Something like:

@media ( min-env(--my-env): 0px ) and ( max-env(--my-env): 20px ) {
  ...
}

I guess this would bring its own set of complexities. I imagine this wouldn't be possible unless we can define the syntax for the env, like Houdini's @property.

@rthrejheytjyrtj545
Copy link

@nasif-co, your use case is already partially resolved with style queries and will be fully resolved if the working group accepts #8376. Sample code: @container style(--my-var: 15px) {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment