Get in touch

I will reply within 1 business day.

Programatic CSS

csshtmljavascript

Has CSS jumped the shark? Is it becoming a programming language? Let's dive into to some of the most controversial changes to the web platform since the depreciation of the blink tag.

Introduction

CSS functions are a long way off being usable in any production applications, but do open up a world of exciting possibilities. Here's 4 use cases.

Media queries

As it's not possible to add css custom properties to the parameters of media queries we still need to pass the media range to every query. Centralising them in a function can be much neater and eliminates the need to memorise the actual ranges. There can now be a single source of truth in a media queries @function file much how we can with JavaScript matchMedia.

@function --xs-md-lg-up(--xs, --md, --lg) {
  result: var(--xs);

  @media screen and (width >= 768px) {
    result: var(--md);
  }

  @media screen and (width >= 1280px) {
    result: var(--lg);
  }
}
div {
    padding: --xs-md-lg-up(16px, 24px, 32px);
}

The result keyword is the equivalent to JavaScript's return, although hilariously the cascade works in @function so an early return is not possible. This means in the following snippet the final active media query will win.

Colours

The relative colour syntax is great although quite verbose. Using @function means you can pass in a custom property and the modifier. You can see for this version we also pass in types. Yes that's right we now live in a world where native CSS has types but JavaScript does not!

@function --opacity(--color <color>, --opacity <number>) returns <color> {
  result: oklch(from var(--color) l c h / var(--opacity));
}

@function --darken(--color <color>, --factor <number>) returns <color> {
  result: oklch(from var(--color) calc(l * var(--factor)) c h);
}

If you pass anything other than a colour into this function the function will silently fail.

div {
    background-color: --opacity(var(--brand-colour), .5);
}

We could add error handling to our function but that's quite enough down that rabbit hole for now.

Dark mode

This function handles dark mode with an option to override. This could be useful for creating component level dark mode.

@function --light-dark(--mode) {
  result: var(--mode);

  @media (prefers-color-scheme: dark) {
    result: if(style(--mode: dark): light; else: dark);
  }
}

This would allow a user defined dark mode with an option to override with a theme.

Icons

OK to finish let's try something truly experimental! In this example we will first set a custom property to be a CSS shape function like so.

:where(html) {
  --icon-triangle: shape(from 0% 0%, line to 0% 100%, line to 100% 50%, close);
}

Next we add an if statement to check the value of --icon.

@function --get-icon(--icon <custom-ident>) {
  result: if(
    style(--icon: arrow-right): var(--icon-triangle);

    else: var(--icon-triangle-left)
  );
}

We can then use that function to set the value of of clip-path on a custom element. Custom elements have wide support but have only just recently been supported by React.

icon-element {
    background-color: currentColor;
    clip-path: --get-icon(attr(icon type(<custom-ident>), none));
    content: "";
    display: block;
    height: 1rem;
    width: 1rem;
}

Finally we can pass the desired icon value to a custom attribute on the custom HTML element.

<icon-element icon="arrow-right"></icon-element>

And there we have it, a CSS & HTML only Icon.