Skip to content

[css-env][css-conditional] Need an approach to check whether an environment variable is supported #3576

Closed

Description

@upsuper

Websites need to detect whether an environment variable is support in order to have special rules applied on it. It seems there is currently no way to do that.

Natively authors would just use something like

@supports (padding-top: env(safe-area-inset-top)) { ... }

for the detection, but the only thing it checks is that env is supported. If you replace safe-area-inset-top with something else like x, all implementations nowadays would still happily accept it. This is problematic for future extensions.

I'm not sure whether #3285 would solve this (and breaks things like env(x) in @supports). If not, we probably need to either special-case the behavior of env in @supports, or introduce a function for it.

Activity

  1. emilio commented on Jan 31, 2019

    @emilio
    Collaborator

    So I thought that was the purpose for the fallback value in env()? But maybe there's a use-case for detecting a particular environment variable?

    Ideally you'd use env(my-var, 10px) or such.

  2. upsuper commented on Jan 31, 2019

    @upsuper
    MemberAuthor

    Yeah, probably fallback is enough... I need to think more about possible usecases.

  3. upsuper commented on Feb 16, 2019

    @upsuper
    MemberAuthor

    Okay... I cannot think of any usecase. Authors may want to specify different style when certain env is available, but conceptually I think what they would actually want would be to make the page responsive to different value of the env, which should be done with media queries, and in that case, fallback should be enough.

  4. fantasai commented on Sep 18, 2020

    @fantasai
    Collaborator

    Unlike variables, the set of supported environment variables isn't open-ended, though. Seems legit to ask about support.

  5. bramus commented on Feb 21, 2025

    @bramus
    Contributor

    I ran into this when trying to feature detect support for the new safe-area-max-inset-bottom envvar introduced in #11019.

    The following check (live demo) always passes in all browsers, because parsers support the syntax:

    body {
    	background: red;
    	@supports (x: env(safe-area-max-inset-bottom)) {
    		background: green;
    	}
    }

    A workaround that I found, is to default to a an unreachable value and then use a style query to check the result (live demo):

    @property --safe-area-max-inset-bottom-support {
    	syntax: "<length>";
    	inherits: true;
    	initial-value: 0px;
    }
    
    :root {
    	--safe-area-max-inset-bottom-support: env(safe-area-max-inset-bottom, -1px); /* When supported the value returned will be 0px or greater */
    	--safe-area-max-inset-bottom: max(0px, var(--safe-area-max-inset-bottom-support));
    }
    
    body {
    	background: green;
    	@container style(--safe-area-max-inset-bottom-support: -1px) {
    		background: red;
    	}
    }

    This however only works in browser that support style queries, which currently excludes Firefox.

    A proper solution would be needed.


    I suggest to extend <supports-feature> with a <supports-env-fn>, like so:

    <supports-feature> = <supports-selector-fn> | <supports-env-fn> | <supports-decl>
    <supports-selector-fn> = selector( <complex-selector> )
    <supports-env-fn> = env( <custom-ident> )
    

    When the passed in <custom-ident> is one of the supported Environment Variables, the result is true. Otherwise it is false.

    Example usage:

    @supports env(safe-area-max-inset-bottom) { … }

    WDYT @fantasai?

  6. progers commented on May 14, 2025

    @progers
    Contributor

    This would be particularly useful for env(preferred-text-scale). A site using this would likely need some conditional styles if the feature is supported. The only approaches that are available are the approach in #3576 (comment), which doesn't work in Firefox, or javascript like the following, which has performance implications due to forcing a style recalc:

    <script>
      let detector = document.createElement('div');
      detector.style = 'display: none; --preferred-text-scale-support: env(preferred-text-scale, -1px)';
      document.body.appendChild(detector);
      let style = getComputedStyle(detector).getPropertyValue('--preferred-text-scale-support');
      if (style.trim().toString() !== '-1px') {
        // supported!
      }
    </script>
    

    @davidsgrogan , @JoshTumath

  7. emilio commented on May 14, 2025

    @emilio
    Collaborator

    I think an env() supports function like selector() seems fine. But also is it really all that useful? It seems for preferred-text-scale you could just assume the fallback is 1, so something like --text-scale: env(preferred-text-scale, 1) and roll with --text-scale.

    I guess the usefulness would be mostly to avoid doing hacky things to compute the scale if you know the browser is giving you a value?

  8. bramus commented on May 14, 2025

    @bramus
    Contributor

    I think an env() supports function like selector() seems fine. But also is it really all that useful?

    Detecting safe-area-max-inset-bottom needs this (see above) because there is a difference between the value being 0 because it is actually 0 (e.g. on desktop) vs the value being 0 because it is the fallback that is used.

    I worked around it in my code by setting the fallback to a value that can never be reached (-1) and then using a style query. However, there is no guarantee that that type of workaround will work for other, future, envvars.

  9. progers commented on May 14, 2025

    @progers
    Contributor

    @emilio , if a site currently relies on browser heuristics for text scaling and wants to switch to their own sizes based on env(preferred-text-scale), they need to first disable the existing heuristics, and then apply their new styles. For example:

      :root {
        @suports(env(preferred-text-scale)) {
          /* disable existing browser heuristics */
          text-size-adjust: none;
    
          /* opt-in to use the preferred text scale */
          font-size: calc(100% * env(preferred-text-scale));
        }
      }
    

    In practice, I think sites will need to apply little tweaks here and there. An example is just removing their existing workarounds for buggy text scaling heuristics:

      .some-element {
        @suports not (env(preferred-text-scale)) {
          /* workaround for browser-xyz autosizing bug # 33124 */
          line-height: 42px;
        }
      }
    
  10. JoshTumath commented on May 14, 2025

    @JoshTumath
    Contributor

    Agreed. I think we would find it useful at the BBC to be able to feature detect env(preferred-text-scale).

    We also don't know what other env() values will be proposed in the future. It may be useful to support feature detecting them now.

  11. bramus commented on Jul 1, 2025

    @bramus
    Contributor

    To summarize, the problem is that the following always passes because of how env() was designed to work:

    @supports (x: env(this-does-not-exist)) { … }

    To fix that, the proposal is to extend <supports-feature> with a <supports-env-fn>, like so:

    <supports-feature> = <supports-selector-fn> | <supports-env-fn> | <supports-decl>
    <supports-selector-fn> = selector( <complex-selector> )
    <supports-env-fn> = env( <custom-ident> )
    

    When the passed in <custom-ident> is one of the supported Environment Variables, the result is true. Otherwise it is false.

    Example usage:

    @supports env(safe-area-max-inset-bottom) { … }
  12. LeaVerou commented on Jul 9, 2025

    @LeaVerou
    Member

    Apologies if I missed it, what’s the downside for making the check parse-time? So far it seems there are only benefits.

    As to whether the fallback value suffices, the entire reason we have @supports in the first place is because it often doesn't. Otherwise authors can always use fallback values and duplicate declarations to provide fallbacks for the specific property being used. But often the fallback involves use of other properties entirely.

  13. css-meeting-bot commented on Jul 9, 2025

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-env][css-conditional] Need an approach to check whether an environment variable is supported, and agreed to the following:

    • RESOLVED: add env() function to @supports
    The full IRC log of that discussion <bramus> `@supports (x: env(this-does-not-exist)) { … }`
    <JoshT> bramus: about feature detecting environment vars
    <JoshT> ... if you do a check like this, the check always returns true
    <JoshT> ... env() only becomes invalid at computed value time, not parse time, so this check always returns true
    <JoshT> ... authors need to check some of the env() vars are supported
    <TabAtkins> q+
    <JoshT> ... depending on support, authors might need to do something else in their CSS
    <JoshT> ... proposing a new function to use inside the @supports
    <JoshT> ... like now you can do @supports selector()
    <lea> q?
    <bramus> `env( <custom-ident> )`
    <JoshT> ... we'd have @supports env() with your custom ident
    <lea> q+
    <astearns> ack TabAtkins
    <JoshT> ... and when the custom-ident is one of the supported env()s, it returns true
    <JoshT> TabAtkins: so I support this, makes sense. fallback is often all that you need but there will be cases where you need to do an alternative design
    <JoshT> ... I saw lea's comment on the thread. 'Why aren't we detecting whether it is known at parse time?'
    <JoshT> ... because we need to be able to do fallback
    <JoshT> ... if it was done at parse time, fallback would not work
    <JoshT> ... could do it in theory, but would be contrary to how other things with fallback work
    <astearns> ack lea
    <emilio> +1 to what tab said
    <JoshT> lea: indeed what I was going to ask
    <astearns> q+
    <JoshT> ... will be web compat concerns at this point
    <JoshT> ... if there is no fallback, maybe it could become valid at parse time, and if not, it would be evaluated as it is now
    <bramus> q+
    <emilio> q+
    <JoshT> ... one issue with that is theoretically you could have a var with the env var and the fallback, and then it wouldn't be possible to know when you have a fallback.
    <JoshT> ... but that's how vars work anyway so won't be a problem
    <JoshT> ... these things don't have a preset known in advance anyway, but if it is known it would not be known at parse time
    <lea> s/will be web compat concerns at this point/might be web compat concerns at this point/
    <JoshT> astearns: in looking at this, I thought there may be places where there needs to be ??? support calls to see where a var will be supported for a particular property
    <kbabbitt> s/???/double/
    <lea> q+
    <astearns> ack astearns
    <lea> q-
    <JoshT> ... but I don't think in practice that will be an issue. env vars are normally just <length> values
    <JoshT> bramus: I fear if we change how env currently works to check at parse time, that would differ from how var works. people may ask why you don't do that for var
    <lea> astearns: Even when you want to test presence in general, without a particular property in mind, it's common practice to just pick one, e.g. to test color validity authors often do @supports(color: foo)
    <astearns> ack bramus
    <JoshT> ... I don't think it makes sense right now because all custom properties come back as supported because you can have any name
    <lea> q+
    <astearns> ack emilio
    <JoshT> emilio: I think there is value in keeping it consistent with var. even with lengths, it will just compute to the initial value of the property when it's not supported, like 'auto'. I wouldn't need to write a fallback. It feels easier to reason about at computed time.
    <JoshT> ... I think we do want to keep env() resolving at computed value time. The simple thing is good: add the new env() query to check it's supported
    <astearns> ack lea
    <JoshT> ... and even if you need to behave as invalid at parse time, you can do @supports and call it a day
    <JoshT> lea: custom properties are like this because they are defined by authors and could change.
    <JoshT> ... it's an unfortunate wart in css that we had to accept to make it possible
    <JoshT> ... but with env() we can do better
    <emilio> q+
    <TabAtkins> q+
    <kbabbitt> q+
    <JoshT> ... right now, it's fixed, the value doesn't change
    <astearns> ack emilio
    <JoshT> emilio: the values do change, so you do still need the env() reference at specified value time. so this would not be like calc() which can simplify most of the time
    <lea> qq+
    <JoshT> ... you still need something in between, so I suggest just keeping it like var()
    <bramus> +1
    <astearns> ack lea
    <Zakim> lea, you wanted to react to emilio
    <JoshT> emilio: you still need to keep the reference
    <JoshT> ... that would significantly change how these work and I still think there's value make it like var()
    <lea> q+ Or we can just define that if the name is known, it's always valid
    <JoshT> ... you cannot get rid of the reference anyway, so I don't think it's as useful as you think. Maybe I'm wrong
    <astearns> ack TabAtkins
    <JoshT> ... having an in between thing where you evaluate them at parse time based on whether the name exists I think is weird
    <JoshT> TabAtkins: I think this discussion is confused. we are not worrying about the type of the value
    <JoshT> ... its a substitution function
    <lea> q+
    <JoshT> ... while the set of envs is static over the course of a page life time, we are adding more envs relatively often so knowing what is supported in a browser is non-trivial
    <emilio> q+
    <JoshT> ... we're worried about the fact that new stuff can be added in a way to predict and engineer around without a query
    <JoshT> ... often the fallback is fine, but when you need more complicated reactions, knowing whether a value exists or not is valuable here
    <JoshT> ... to have an @supports env() that checks if it exists is useful here
    <astearns> ack kbabbitt
    <JoshT> kbabbitt: on the topic of env vars changing, I remember a proposal to have author defined global values of some kind
    <JoshT> ... not sure if it was env vars but that could be useful for setting values for the entire page
    <JoshT> ... was that proposed as an extension to env vars, because then it could be extended by author definitions
    <lea> q?
    <JoshT> TabAtkins: the discussion never settled on that, whether var or env.
    <JoshT> ... regardless, they are distinct because the keyword would be double dashed --
    <JoshT> ... I agree to treat env as like var for simplicity
    <JoshT> ... but we don't know how that would affect the rest of the discussion
    <JoshT> lea: +1 to the problem
    <astearns> ack lea
    <JoshT> ... I agree it should be possible to check if a name is known and not known
    <JoshT> ... the question is the way to do it
    <astearns> q+
    <JoshT> ... what is being proposed, it only makes some of these uses invalid at parse time. like with a made up env name, it would always be invalid
    <bramus> q+
    <JoshT> ... if the name is known, same behaviour as today. the only case that is different is when the name is not knowm
    <JoshT> ... it seems better to improve the API surface than teach a whole new function
    <JoshT> ... if we have a new API surface and a new function, authors would have to look it up
    <JoshT> ... and custom envs would need to use -- dash dash anyway
    <astearns> ack emilio
    <JoshT> emilio: When I mentioned typing, I was wondering how we could diverge while parsing
    <JoshT> lea: but there's no substitution. you know the name.
    <JoshT> ... if a var is used, it's a different story
    <JoshT> emilio: but right now, we don't check the value at parse time.
    <JoshT> q+
    <JoshT> emilio: it has a performance cost to change root properties after the page is loaded because you need to inherit it in the whole DOM
    <kbabbitt> +1 emilio
    <astearns> ack astearns
    <lea> q+
    <JoshT> astearns: I am agnostic to these approaches. I'm concerned about the difference of how @supports works with whether there is a fallback or nor
    <JoshT> ... it would change the validity if you're asking for an invalid env var
    <JoshT> TabAtkins: It would just be asking about the name
    <astearns> ack bramus
    <JoshT> bramus: Regarding what lea proposed, if the custom ident is an env var, I think that would allow to do something like:
    <bramus> `@supports (background: env(safe-area-inset-left)) { … }`
    <JoshT> ... this seems not good because the pixel value is not the value of the background
    <JoshT> lea: it would be parsed as a background position and everything would still go back to the initial value
    <emilio> q+
    <JoshT> bramus: I think we should still use the @supports env() function because otherwise authors would still have to look up when an env() value works in @supports works
    <JoshT> astearns: It could be an issue that the supported version of env ????? will fail to work properly if it has a fallback
    <TabAtkins> The suggestion is to allow `@supports env(foo, fallback)` and just ignore the fallback
    <romain> q+
    <astearns> ack fantasai
    <Zakim> fantasai, you wanted to suggest we adopt the proposal and discuss the parse-time validity of env() in its own issue
    <JoshT> bramus: we could then also opt to change the @supports to accept the fallback, which then wouldn't take it into consideration, but maybe that's not a good thing
    <TabAtkins> (I don't think this is a good idea.)
    <romain> q-
    <astearns> s/supported version of env ?????/@supports version of env(), copy-pasted env()/
    <JoshT> fantasai: I am sympathetic to lea's option. but I think that's a separate argument to whether we have @supports env()
    <lea> TabAtkins: ideally we could validate prop: fallback if foo is unknown, but that's less importan than env(unknown) being invalid IMO
    <JoshT> ... even if we make that work, having a shorthand to support this is still useful
    <emilio> +1
    <JoshT> ... that would let us accept this proposal and we'd still consider to discuss if you could evaluate env() at parse time
    <JoshT> ... but regarding the proposal, I don't think it should accept a fallback
    <lea> +1 fantasai
    <JoshT> ... the top level functions are specific about what they accept
    <TabAtkins> I think Emilio and Bramus's point about consistency of operation is important. env() is mostly identical to var(), giving it different validity behavior makes things harder to learn.
    <JoshT> ... it could be checkable in some way but I don't think we should allow that. we should accept the keyword
    <kbabbitt> JoshT: there is a difference between how env and var work
    <kbabbitt> ... because env works in mqs as well
    <astearns> ack lea
    <kbabbitt> ... but I don't think that's currently supported in any browser
    <JoshT> lea: as I wrote on the IRC, we could invalidate property fallback if the name is unknown, but that is a second tier thing
    <JoshT> ... but that would be the north star. then you could envaluate if ????
    <astearns> ack JoshT
    <JoshT> ... that is now app support works in general. it's already taught that that's how @support works anyway.
    <JoshT> ... consistency has been talked about. it's an ergonomic issue
    <JoshT> ... the way var works now anyway is an ergonomic issue
    <JoshT> ... there is a TAG principle on consistency.
    <astearns> zakim, close queue
    <Zakim> ok, astearns, the speaker queue is closed
    <JoshT> ... but there are cases where it is warranted to break consistency if the other option has better ergonomics
    <romain> +1
    <JoshT> ... I would argue there are cases where people currently struggle. this is a pain point.
    <TabAtkins> The var() behavior (invalid at comptued value time) is unavoidable and shows up in several places now. Similarly, `attr(nonsense)` is valid at parse time. Etc. env() being the sole exception of substitution functions that can be invalid at parse time, *depending on what you write*, isn't great.
    <JoshT> ... the prority of constituencies relates. we don't want authors to learn a new thing.
    <JoshT> emilio: quick suggestion
    <astearns> ack emilio
    <JoshT> ... it seems everyone agrees checking whether env is supported is a useful thing
    <JoshT> ... was going to suggest we move how env parses to a separate issue
    <lea> @support env() is nice regardless, even as a shortcut. And it does fix the immediate issue so +1 to it
    <JoshT> ... changing how env works has a perf cost and compat risk and it's more implementation and research cost
    <JoshT> extending @supports is easier
    <fantasai> +1
    <bramus> +1
    <ydaniv> +1 to emilio
    <JoshT> ... if we end up with two ways to test how env() works, I think that's a relatively small cost to pay
    <lea> astearns: I think we have consensus to add @supports env(name)
    <fantasai> s/astearns:/astearns,
    <kbabbitt> +1 to @supports env(name)
    <JoshT> ... would anyone object to adding @supports env() to the top level syntax
    <JoshT> RESOLVED: add env() function to @supports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions