[csswg-drafts] Proposal: Custom CSS Functions & Mixins (#9350)

mirisuzanne has just created a new issue for https://github.com/w3c/csswg-drafts:

== Proposal: Custom CSS Functions & Mixins ==
## Background

[Read the full explainer](https://css.oddbird.net/sasslike/mixins-functions/)

There's an existing issue for [Declarative custom functions](https://github.com/w3c/csswg-drafts/issues/7490), 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](https://www.smashingmagazine.com/2019/07/css-custom-properties-cascade/) like the [space toggle hack](https://lea.verou.me/blog/2020/10/the-var-space-hack-to-toggle-multiple-values-with-one-custom-property/), and recently style queries. There's also an open issue for [Higher level custom properties](https://github.com/w3c/csswg-drafts/issues/5624) 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:

```css
/* 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>`. To re-use existing custom-property syntax, we could do something like:

```css
@function --example (
  --name-only;
  --name-with: default-value;
  @parameter --all-three {
    default: 2em;
    syntax: "<length>";
  }
) { /* … */ }
```

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. The `@parameter` rule is particularly clunky at this point. Or perhaps it could also move inside the function/mixin body instead of living in the prelude?

Internally, both syntaxes should allow conditional at-rules such as `@media`, `@container`, and `@supports`. I don't think non-conditional or name-defining at-rules would serve any purpose, and should likely be discarded.

Both mixins and functions would be resolved during variable substitution, and the resulting computed values would inherit (the same as custom properties).

## 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 `@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:

```css
@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);
}
```

## Mixins

Mixins, on the other hand, will mostly contain CSS declarations and nested rules that can be output directly:

```css
@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:

```css
@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](https://css.oddbird.net/sasslike/mixins-functions/) for some further notes, including a suggestion from @astearns for eventually providing [a builtin keyframe-access mixin](https://css.oddbird.net/sasslike/mixins-functions/#keyframe-based-mixins-for-interpolated-values) to help address the [responsive typography interpolation](https://github.com/w3c/csswg-drafts/issues/6245#issuecomment-1715416464) use-case.


Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9350 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 13 September 2023 08:13:09 UTC