Description
Background
There's an existing issue for Declarative custom functions, which forms the basis for much of this proposal. I'm opening a separate issue here, in order to make a broader proposal building on that, which includes both functions and mixins.
From a language perspective, mixins and functions are somewhat distinct – they live at different levels of the syntax, and come with different complications. It may make sense to handle them in different levels of a specification (likely functions first) or even different specifications altogether. Function-specific discussion could move back to the existing thread for that work. However, the features also have a lot in common from the author/syntax perspective, so I wanted to consider them together here, without cluttering the main thread.
Intro
Both Mixins and Functions provide a way to capture and reuse some amount of logic. That can be used for the sake of developer shorthands, and also as a way of ensuring maintainability by avoiding repetition and encouraging consistent use of best practice patterns. For many years, authors have been using pre-processors to perform this sort of CSS abstraction – or experimenting with custom property tricks like the space toggle hack, and recently style queries. There's also an open issue for Higher level custom properties with many mixin-like use-cases.
By providing a native CSS solution for these use-cases, we can help simplify web tooling/dependency requirements – while also providing access to new functionality. Mixins and functions in the browser should be able to accept custom property arguments, and respond to client-side media, container, and support conditions.
The overlapping syntax basics
Both functions and mixins need to be defined with a (custom-ident) name
, a parameter-list
, some amount of built-in-logic, and some output
to return. The difference between the two is where they can be used in CSS, based on the type of output they provide:
- Functions return CSS values (like a string, color, or length) and can be used inside a CSS declaration
- Mixins return entire CSS declarations or even rule blocks (including selectors and other at-rules)
For the basics, I'm proposing two new at-rules following a similar pattern:
/* custom functions */
@function <name> [(<parameter-list>)]? {
<function-logic-and-output>
}
/* custom mixins */
@mixin <name> [(<parameter-list>)]? {
<mixin-logic-and-output>
}
The parameter lists should be able to define parameters with a (required) name
, an (optional) default
, and potentially (optional) <syntax>
. In order to allow custom-property values with commas inside, we likely need a ;
delimiter both in defining and passing arguments, where values are involved. To re-use existing custom-property syntax, we could do something like:
@function --example (--named-parameter; --name-with: default-value) {
/* if further description of a parameter is necessary */
@parameter --named-parameter {
default: 2em;
syntax: "<length>";
}
}
[Edited] @emilio has suggested potentially having parameter names only in the parameter list, and then @parameter
-like rules in the body of the function/mixin when default values or syntax descriptor are needed. That would remove the need for ;
delimiters in the prelude entirely. I'm not attached to all the details of the syntax here, but borrowed from existing structures. If we don't need the syntax
definition for parameters, or can add that later, it might allow us to simplify further.
Internally, both syntaxes should allow conditional at-rules such as @media
, @container
, and @supports
. That's one of the primary functions that CSS-based functions and mixins could provide. I don't think non-conditional or name-defining at-rules would serve any purpose, and should likely be discarded.
Functions
Normal properties inside a function would have no use, and could be discarded and ignored. However, it would be useful for functions to have internally-scoped custom properties. To avoid accidental conflicts, internal function logic would not have access to external custom properties besides the values explicitly passed in to the defined parameters.
In addition to allowing (scoped) custom properties and conditional at-rules, a function would need to define one or more resulting values to return. I like the at-rule (e.g. @return
) syntax suggested in the original thread, though the result
descriptor could also work. If more than one value would be returned, the final one should be used (to match the established last-takes-precedence rules of the CSS cascade).
An example function with some conditional logic:
@function --at-breakpoints(
--s: 1em;
--m: 1em + 0.5vw;
--l: 1.2em + 1vw;
) {
@container (inline-size < 20em) {
@return calc(var(--s));
}
@container (20em < inline-size < 50em) {
@return calc(var(--m));
}
@container (50em < inline-size) {
@return calc(var(--l));
}
}
h1 {
font-size: --at-breakpoints(1.94rem, 1.77rem + 0.87vw, 2.44rem);
padding: --at-breakpoints(0.5em, 1em, 1em + 1vw);
}
Functions would be resolved during variable substitution, and the resulting computed values would inherit (the same as custom properties).
Mixins
Mixins, on the other hand, will mostly contain CSS declarations and nested rules that can be output directly:
@mixin --center-content {
display: grid;
place-content: center;
}
body {
@apply --center-content;
/*
display: grid;
place-content: center;
*/
}
I don't believe there is any need for an explicit @return
(though we could provide one if necessary). Instead, if there is any use for mixin-scoped or 'private' custom properties, we could consider a way to mark those specifically. Maybe a flag like !private
would be enough?
Another possible example, for gradient text using background-clip when supported:
@mixin --gradient-text(
--from: mediumvioletred;
--to: teal;
--angle: to bottom right;
) {
color: var(--from, var(--to));
@supports (background-clip: text) or (-webkit-background-clip: text) {
--gradient: linear-gradient(var(--angle), var(--from), var(--to));
background: var(--gradient, var(--from));
color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
}
h1 {
@apply --gradient-text(pink, powderblue);
}
There are still many issues to be resolved here, and some syntax that should go through further bike-shed revisions. Read the full explainer for some further notes, including a suggestion from @astearns for eventually providing a builtin keyframe-access mixin to help address the responsive typography interpolation use-case.
Metadata
Metadata
Assignees
Type
Projects
Status
Status