-
Notifications
You must be signed in to change notification settings - Fork 667
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
Declarative custom functions #7490
Comments
I'd assume that the argument syntax validation would match what's done for One issue that might (but I'm not sure) be difficult (although maybe more for designing/specifying the feature than for implementing it) is determining when the functions are resolved. (Does it work like The syntax you propose seems generally reasonable, although there are various ways it could be changed. For example, I probably wouldn't do both of requiring There's also a question of whether this could interact with features like |
Somewhat related, maybe? depending on how you squint at it? w3c/css-houdini-drafts#857 |
Definitely. Thanks for pointing me to that issue 🙏🏼 I think the main difference was that this proposal was limited to mathematical or "pure" functions. They could be defined directly in css, and I hoped they could be easier to implement and polyfill than fully-fledged functions. The houdini proposal is much more powerful and would allow you to back the custom functions with js. I don't think there's a need for both custom mathematial functions, and proper/complex custom functions. Closing this issue, hoping that there will be a way to declare simple functions directly in css once the |
fwiw, my intent wasn't to suggest that this issue should be closed, but rather to link up history and use cases I was aware of so that it's easy to keep it all in mind. |
Yeah, I agree that this is worth keeping open in case we want to do this first. |
It would just be variable substitution, nothing more, nothing less. (Well, something more - we validate the arguments per the syntax as a help to the author, identical to registered properties. So still "just" variable substitution.) The fact that the OP example is a math function has nothing to do with the custom function part, it just means that when it var-substitutes you get left with a math function, same as
My suggested syntax just used var() and normal custom property names for simplicity, and to underscore the fact that this is just var substitution under the covers. It also means you get the same var() behaviors you'd get elsewhere, like the ability to specify fallback values (useful if the argument doesn't match its declared syntax, as it'll sub the guaranteed-invalid value otherwise, per standard variable rules). That said, it's possible we may want to do smarter stuff for the arguments, in which case it would make sense to have a different function. I just can't come up with any "smarter stuff" yet.
That's the nice thing about making it just a slightly special case of variable substitution - we've already got answers to those questions. ^_^
Definitely related. This would be a simple declarative version of the more full-featured, JS-driven Houdini feature linked there. I imagine they'd live in the same namespace and registered Houdini functions override @function declarations of the same name. They should already have similar behavior in many respects; that is, the Houdini feature will also be essentially just variable substitution, but with JS providing the substitution value. |
I tried to update the proposal text based on the feedback and my (hopefully) improved understanding:
The original text is left in an expandable details element for context. |
Do we need initial argument values for when the function is referenced with too few arguments? I think it would be sufficient if the initial values of arguments always are Providing an argument with wrong syntax would also result in a example: initial values for arguments@custom-function --double-length(--length) {
arg-syntax: --length "<length>";
/* could be ambiguous with multiple arguments (i.e. one is "<length>#") */
arg-initial-values: 0px;
result: calc(2 * var(--length, 20px));
}
.use {
height: --double-length(); /* 0px */
width: --double-length(red); /* 40px */
} example: missing arguments results in guaranteed-invalid values@custom-function --double-length(--length) {
arg-syntax: --length "<length>";
result: calc(2 * var(--length, 20px));
}
.use {
height: --double-length(); /* 40px */
width: --double-length(red); /* 40px */
} |
Is there a need for a result syntax and a fallback value? If declarative custom functions were to be used in animations, the result should have a declared syntax. In addition, one should probably be able to provide a fallback value if the result expression is invalid. @custom-function --flat-color(--color) {
arg-syntax: --color "<color>";
result: var(--color), var(--color);
result-syntax: "<color>#";
fallback: red, red;
}
.valid-use {
background: linear-gradient(--flat-color(green)); /* green */
}
.invalid-use {
background: linear-gradient(--flat-color()); /* red */
} Would a single space be allowed as an argument, similar to a space in a custom property declaration? --valid-declaration: ; /* single whitespace */
@custom-function --identity(--input) {
arg-syntax: --input "*";
result: var(--input);
}
.use {
--valid-declaration: ; /* single whitespace */
--also-valid-declaration: --identity( ); /* single whitespace */
--invalid-declaration: --identity(); /* no argument, guaranteed-invalid */
} If so, I guess this could be used to create an --if-else function using the space toggle trick (not that we want to do this): @custom-function --if-else(--condition, --result-if-true, --result-if-false) {
arg-syntax: --condition "*", --result-if-true "*", --result-if-false "*";
result: var(--condition) var(--result-if-true);
result-syntax: "*";
fallback: var(--result-if-false);
}
.use-with-true-condition {
background: --if-else( , green, red); /* green */
}
.use-with-false-condition {
background: --if-else(initial, green, red); /* red */
} |
Another question, sorry for the spam: Would the initial value of So that you could declare a function that always returned invalid? @custom-function --invalid() {
result: initial;
/*
if there is a return-syntax, that would probably have to be the universal syntax definition
to skip fallback and allow result to return the initial `guaranteed-invalid value`*/
result-syntax: "*";
}
/* So that this */
.with-custom-function {
some-prop: --invalid(); /* Guaranteed invalid value */
}
/* Would be equal to this */
.with-custom-prop {
--invalid: initial;
some-prop: var(--invalid); /* Guaranteed invalid value */
} |
Another question regarding argument syntax. (sorry). Is there anything stopping us from declaring the syntax inline with the arguments? /* Could we do this (inline) */
@custom-function --sum(--argument1 "<number>", --argument2 "<number>") {
result: calc(var(--argument1) + var(--argument2));
}
/* Instead of this? (separate syntax declaration) */
@custom-function --function(--argument1, --argument2) {
arg-syntax: --argument1 "<number>", --argument2 "<number>";
result: calc(var(--argument1) + var(--argument2));
} |
As you note, just ensuring that unpassed arguments produce invalid variables suffices, since you can use variable fallback to supply the default value then. It might still be useful to have a default value expressed more declaratively, purely as an authoring convenience.
No, a result syntax isn't needed. Custom properties need a syntax to enable animation of the properties, but you don't need to do anything special to enable animation of a value with a A fallback used when the passed arguments don't match their declared syntaxes makes sense! And if no fallback is specified, it just operates normally with those arguments resolved to the invalid value, so you can handle them with fallback if desired.
Note that the distinction between
It would be a required declaration, so there wouldn't be an initial value. (And declarations don't accept the CSS-wide keywords unless they're specified to.) You can still make a function that's guaranteed to be invalid in various ways; for example, writing an invalid
Nope, and that's indeed probably better. |
Do the arguments need the |
You reference the argument value via |
As I understand it, that naming restriction is in place because custom properties are declared in rule blocks alongside regular properties so the prefix is necessary to avoid collisions. The formal parameters are not declared in rules so there's no such collisions to avoid. If the issue is the definition of |
Conditionally-valid syntax isn't great API design, in general. If it's excessively troublesome to use the generally-applicable syntax, it can be worthwhile making a specialized version, but in this case it's literally the two characters It would also restrict our ability to evolve the |
Amazing discussion so far! It's exciting to see and keep up with all the changes coming. I've been giving quite a bit of thought to custom CSS function defs this past week before @tabatkins informed me yesterday that this thread already existed (thanks btw Tab!). It's awesome to see that a lot of the same thoughts and considerations I had have already been voiced and taken into account here. A few ideas I'd add which I'm hoping will stir continued fruitful discussion— TL;DR (aka Table of Contents)
1. Arg names, arg syntaxes, and function return syntax before def block
|
I think the idea here is to solve declarative custom functions through regular var substitution, so some of these might be difficult to achieve. There is a more fully fledged custom function proposal in Houdini where the function will be defined in JS. I think that proposal would solve most of your needs. There seems to be consensus for having arg names and syntax before the definition block, so I'll update the proposal to reflect this. Intermediate values and recursion might be doable, but I think the experts will have to look into that :) |
Is this proposal and w3c/css-houdini-drafts#857 mutually exclusive (under the same function syntax), or can they co-exist? It would be great if they could co-exist, for evolution as well, e.g. you start off with a custom function that is just syntactic sugar, then down the line realize you need JS, you can just switch to JS without having to rewrite all your code. |
Hopefully :) I closed the issue when I was made aware of w3c/css-houdini-drafts#857 but it was reopened by @dbaron opened after this comment:
@tabatkins wrote later the same day:
|
@johannesodland @LeaVerou @tabatkins @dbaron I 100% agree that Houdini functions will be a huge advantage to CSS users. However, most instances I can imagine using CSS’s In my experience, it’s often very helpful to clearly divide styles from scripts when possible. Functions that are purely style-related can stay in the CSS and be fully capable, supporting conditional logic using native CSS if-statements, short-circuiting, recursion, and more, all without a JS counterpart as outlined in my previous comment. I understand some of those features weren’t included in the original spec outlined in this issue, however, my vision—before knowing of this spec—and this spec are quite closely aligned, and I believe most/all features I mentioned is already possible using existing CSS syntax. I also believe such features and enhancements would make CSS functions vastly more useful and desirable overall. Per @LeaVerou’s comment, there could very well be used as fallback functions in some instances until the JS loads, but I also think these functions could stand strong on their own in many instances without needing JS. |
UPDATE: I want to clarify— my main goal here in voicing my recommendations is evolving CSS itself and empowering CSS developers to build and use powerful functions with ease without needing to use JS if they so choose. Houdini is certainly the primary solution to the most complex use cases. I'm certainly not suggesting CSS Even more important than utility cases, some of my recommendations such as using
|
A case for
|
@brandonmcconnell That should probably be tracked in it's own issue (if it doesn't have one already), and link back to this thread for use-cases. |
@mirisuzanne That's totally understandable. I'll create that and link it back here shortly. Thanks! |
I also think it's worthwhile to this proposal for declarative functions that they also support non-pure functions. It would be great if functions could support Perhaps to make these functions safer when not pure, we could require that The same rules would also apply for |
fyi (for followers of this ticket) — @mirisuzanne opened a related ticket with a built-out explainer here: #9350 |
@tabatkins mentioned the idea of declarative custom functions in a comment on the custom units issue:
This seems too good to not follow up on. Custom functions would make it possible to write more readable css with less repetition.
Background
We can already increase readability and reduce repetition of expressions by using function like custom properties, as @mirisuzanne describes in her excellent article on CSS Custom Properties In The Cascade. This is available in browsers today.
There are however limits to what we can do with function-like custom properties, and there are issues with their readability.
Example
The following expression returns 0% at a minimum viewport size of 375px, and 100% at a maximum viewport size of 1920px.
We could give this expression semantic meaning and reuse it almost everywhere by declaring it as the value of a custom property:
This custom property could then be used with "arguments" where needed:
This is great, and should work in browsers today (except for divide by unit, that is specified in css-units-4 but not supported yet).
There are some limitations to this approach:
--min-width
and--max-width
are arguments for--fluid-ratio
, nor that--fluid-ratio
is a function like custom property.Proposal
Updated based on feedback. The original text is available below for context.
Add syntax for declarative custom functions. The syntax could follow the example from @tabatkins comment with an at-rule to declare the function and the syntax of its arguments:
This would work through regular var substitution with a slightly different handling of the arguments. The custom function could then be used anywhere a var function can be used:
As this is based on variable substitution it is not limited to math functions and can return any value. It could be used to return one of background patterns from @LeaVerou's pattern gallery:
There's an existing proposal for JS backed custom functions in Houdini. This proposal is related and would be a simple declarative version of that proposal, where in Houdini the substituted value would be provided by JS.
Original proposal text
### Proposal
Add syntax for declarative mathematical functions. The syntax could follow the example from @tabatkins comment with an at-rule to declare the function and the syntax of its arguments:
The custom function could then be used where mathematical expressions can be used:
There are probably many reasons why this would be annoying or impossible to implement, but hopefully there's smarter people than me out there that can come up with a way to solve custom functions.
The text was updated successfully, but these errors were encountered: