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-conditional] prefer() function #7881

Closed
brandonmcconnell opened this issue Oct 13, 2022 · 5 comments
Closed

[css-conditional] prefer() function #7881

brandonmcconnell opened this issue Oct 13, 2022 · 5 comments

Comments

@brandonmcconnell
Copy link

brandonmcconnell commented Oct 13, 2022

The problem

I just recently became more increasingly aware of how CSS doesn't provide a built-in method for providing fallback values, outside of what's currently supported within var() and what's coming to attr().

With this in mind, if I wanted to provide multiple fallback values for an expression based on the first that works, the general approaches are these—

  1. Declare the property multiple times, in priority order (ascending):

    element {
      color: black;
      color: currentcolor;
      color: color(display-p3 1 0 0.87);
    }
  2. Custom properties
    If, however, you want to bring custom properties into the mix, it suddenly becomes a bit more complicated, as var() always validates as truthy, so you instead need to rely on var's built-in fallback value (<declaration-value>).

    element {
      color: var(--property-1, var(--property-2, var(--property-3, black)));
    }

As is clear in the second example above, nesting in this way can get pretty messy, not to mention that this method doesn't support multiple fallback values taking into current account browser support.

With the same type of nesting and incorporating the if() function currently being discussed, it might look something more like this:

element {
  color: var(--property-1, var(--property-2, var(--property-3, if(supports(background: color(display-p3 1 0 0.87)), color(display-p3 1 0 0.87), black))));
}

This same pattern could be used to implement colors in color spaces many browsers don't support, and that's still just one example using color for the sake of this example.

And still… this example is awfully messy.

Description of the proposal

I propose adding a prefer() (or something similar; I'm not sold on the name), which takes a list of values and returns the first value that doesn't error out (or that doesn't return an undefined value).

Syntax

prefer(value1 [, value2?, value3?, ..., valueN?])

Usage

element {
  color: prefer(--property-1, --property-2, --property-3, color(display-p3 1 0 0.87), black);
}

It's important to note, as my previous examples might be found misleading, that this would not change the existing functionality of var(). Take for example the same example from above, but wrapping each custom property name with var():

element {
  color: prefer(var(--property-1), var(--property-2), var(--property-3), color(display-p3 1 0 0.87), black);
}

Because var() always validates as truthy, the above example would always evaluate to var(--property-1) even if its value is undefined. This is important so as, not to clash with the existing functionality of var(). This is still helpful, as variables can still be used with their own fallback values if the variable value is undefined.

As such, these two would be equivalent ✅

element {
  color: prefer(--property, color(display-p3 1 0 0.87), black);
}
element {
  color: prefer(var(--property, color(display-p3 1 0 0.87)), black);
}

These two are not ❌

element {
  color: prefer(var(--property, color(display-p3 1 0 0.87)), black);
}
element {
  color: prefer(var(--property), color(display-p3 1 0 0.87), black);
                /* └→ always evaluates to var(--property-1) */
}

So generally, speaking, this:

element {
  color: prefer(--property, color(display-p3 1 0 0.87), black); /* ✅ */
}

…would be preferred over this:

element {
  color: prefer(var(--property, color(display-p3 1 0 0.87)), black); /* 🤷🏻‍♂️ */
}

…though I can certainly foresee IRL examples where the latter would be what is evaluated (but not actually used) simply because one variable value used inside prefer() has its own fallback value already.

For example:

parent {
  --default: color(display-p3 1 0 0.87);
  --property: var(--default, color(display-p3 0 1 0.87));
  element {
    color: prefer(--property, black);
  }
}

This would be perfectly acceptable and would be the equivalent of this:

parent {
  element {
    color: prefer(color(display-p3 1 0 0.87), color(display-p3 0 1 0.87), black);
  } /*              ↑                           ↑                           ↑
               --default                   --property                     black    */
}
@brandonmcconnell
Copy link
Author

brandonmcconnell commented Oct 13, 2022

This is somewhat related to @jimmyfrasche's issue #4731 and would likely fall into the same category as those functions

#4731: [css-values] Iverson bracket functions: if(), media(), supports(), defined()

It may be worth linking these issues if there's a mechanism for that, beyond this mere mention

@brandonmcconnell
Copy link
Author

brandonmcconnell commented Oct 13, 2022

I chatted with @joshvickerson about this idea on Twitter, and he proposed the function name prefer() which I highly…"prefer" 😅🤦🏻‍♂️… to fallback(), so I'm adjusting this issue with that name change 🎉

@brandonmcconnell brandonmcconnell changed the title [css-conditional] fallback() function [css-conditional] prefer() function Oct 13, 2022
@Loirooriol
Copy link
Contributor

Loirooriol commented Oct 13, 2022

This is basically #5055, but with especial behavior for variables. Some notes:

  • This should use semicolons instead of commas, since properties like background accept values that contain top-level commas.

  • Your proposal treats --property-1 as a reference to the value of that custom property. But --property-1 is an identifier, and various property grammars accept identifiers, which you may also want to specify as a fallback.

    @counter-style --foo { system: symbolic; symbols: '|'; }
    div {
      --foo: lower-alpha;
      list-style-type: prefer(nonsense(); --foo); /* does --foo refer to lower-alpha or to the counter style? */
    }

@brandonmcconnell
Copy link
Author

Great points across the board. Closing this ticket out in favor of @tabatkins' nearly identical one 👏🏼

@brandonmcconnell
Copy link
Author

Duplicates #5055

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants