Skip to content

Declarative custom functions #7490

Open
@johannesodland

Description

@johannesodland

@tabatkins mentioned the idea of declarative custom functions in a comment on the custom units issue:

Like, pretend for a moment that we have simple custom functions, like:

@custom-function --fluid(--value) {
  arg-syntax: --value "<number>";
  result: clamp(1rem, var(--value) * 1vw, var(--value) * 1rem);
}

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.

clamp(0%, 100% * (100vw - 375px) / (1920px - 375px), 100%)

We could give this expression semantic meaning and reuse it almost everywhere by declaring it as the value of a custom property:

* {
    --min-width: 0px;
    --max-width: 2560px;
    --fluid-ratio: clamp(0%, 100% * (100vw - var(--min-width)) / (var(--max-width) - var(--min-width)), 100%);
}

This custom property could then be used with "arguments" where needed:

p {
  --min-width: 375px;
  --max-width: 1920px;
  font-size: mix(var(--fluid-ratio), 1rem, 1.25rem);
}

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:

  1. It's not possible to read from the rule that --min-width and --max-width are arguments for --fluid-ratio, nor that --fluid-ratio is a function like custom property.
  2. We would run into trouble if we wanted to reuse the same "function" with separate parameters.
p {
  /* This would not work. (Arguably a constructed example) */
  --min-width: 375px;
  --max-width: 1920px;
  font-size: mix(var(--fluid-ratio), 1rem, 1.25rem);

  --min-width: 375px;
  --max-width: 700px;
  padding: mix(var(--fluid-ratio), 1rem, 2rem);
}

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:

@custom-function --fluid-ratio(--min-width "<length>", --max-width "<length>") {
   result: clamp(0%, 100% * (100vw - var(--min-width)) / (var(--max-width) - var(--min-width)), 100%);
} 

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:

p {
  font-size: mix(--fluid-ratio(375px, 1920px), 1rem, 1.25rem);
  padding: mix(--fluid-ratio(375px, 700px), 1rem, 2rem);
}

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:

@custom-function --checkerboard(--size "<length>") {
   result: linear-gradient(
        45deg,
        silver 25%,
        transparent 25%,
        transparent 75%,
        silver 75%
      )
      0px 0px / var(--size) var(--size),
    linear-gradient(
        45deg,
        silver 25%,
        transparent 25%,
        transparent 75%,
        silver 75%
      )
      calc(var(--size) / 2) calc(var(--size) / 2) / var(--size) var(--size);
}

.used {
  background: --checkerboard(32px);
}


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:

@custom-function --fluid-ratio(--min-width, --max-width) {
   arg-syntax: --min-width "<length>", --max-width "<length>";
   result: clamp(0%, 100% * (100vw - arg(--min-width)) / (arg(--max-width) - arg(--min-width)), 100%);
} 

The custom function could then be used where mathematical expressions can be used:

p {
  font-size: mix(--fluid-ratio(375px, 1920px), 1rem, 1.25rem);
  padding: mix(--fluid-ratio(375px, 700px), 1rem, 2rem);
}

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.

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