Future of CSS: Functions and Mixins
Andrew Bone

Andrew Bone @link2twenty

About: A British web developer, that is passionate about web accessibility.

Location:
Britain, Europe
Joined:
Jun 8, 2017

Future of CSS: Functions and Mixins

Publish Date: Feb 15 '24
69 24

Today we're going to look at something that hasn't yet been incorporated into the official CSS specification but could change the way we write CSS. I am, of course, talking about Functions and Mixins.

Here's the TL;DR, CSS Functions and Mixin add a way to capture and reuse CSS logic without the use of pre-processors. This allows DRY principles to be executed in CSS without the need of utility classes.

Let's dive in and see how we got here and what these changes may mean in practice.

CSS pre-processors

Traditionally CSS lacked features such as variables, nesting, mixins, and functions. This was frustrating for Developers as it often led to CSS quickly becoming complex and cumbersome. In an attempt to make code easier and less repetitive CSS pre-processors were born. You would write CSS in the format the pre-processor understood and, at build time, you'd have some nice CSS. The most common pre-processors these days are Sass, Less, and Stylus. Any examples I give going forward will be about Sass as that's what I'm most familiar with.

Sass logo

Pre-processors were not without their problems though; for one, the browser can't understand them, you need a build/compile step before you can see what they're doing. For another, each pre-processor has a slightly different syntax for devs to learn and master.

Perhaps as an attempt to remedy this or perhaps just a way to improve vanilla DX (Developer eXperience) the CSS spec began to incorporate aspects of Pre-processors with variables and more recently nesting.

Sass @function

Functions allow you to define complex operations on SassScript values that you can re-use throughout your stylesheet. They make it easy to abstract out common formulas and behaviours in a readable way.

Functions in Sass allows a developer to calculate a single value. This will be calculated at build time so can not include vanilla CSS Variables.

Sass @mixin

Mixins allow you to define styles that can be re-used throughout your stylesheet. They make it easy to avoid using non-semantic classes like .float-left, and to distribute collections of styles in libraries.

Mixins in Sass allows a developer to @include a whole block of css (property and value pairs). It also allows you to pass in arguments that can modify the block. Though, again as this is calculated at build time vanilla CSS Variables aren't available.

Examples and use cases

Let's suppose we have a design system where we have buttons that can be different colours, each button should look the same apart from the different background colour, the hover and active states are slightly darker variants of their base colour.

First let's make a function that will mix any colour with black at a set percentage.

@function --darken(--base, --percent) {
  @return color-mix(in srgb, var(--base), #000 var(--percent));
}
Enter fullscreen mode Exit fullscreen mode

Now that we have a function that can darken our base colours let's make a mixin that will make our default button style with the hover and active states, we can use nesting in the mixin to make this easier.

You'll notice the --color argument has a default colour which will be used if one isn't supplied.

@mixin --button(--color: #174D7C) {
  background-color: var(--color);
  color: white;
  border: none;
  padding: 0.5rem 1rem;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;

  &:hover {
    background-color: --darken(var(--color), 5%);
  }

  &:active {
    background-color: --darken(var(--color), 10%);
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally let's make three buttons one using the default colour then a green and also an orange one.

.primary-btn {
  @apply --button
}

.green-btn {
  @apply --button(#6F9F9C)
}

.orange-btn {
  @apply --button(#FE5F55)
}
Enter fullscreen mode Exit fullscreen mode

Three buttons matching the colour themes

And just like that we have 3 beautiful buttons with the bulk of our CSS only being written once and everything being calculated at run time. Meaning we get full reign of native CSS variables.

Fin

Just when we think the faucet of CSS changes has been shut off something huge like this comes our way. Whilst it will take a while to be finalised into spec and then a while more roll out to browsers the possibilities are simply mind boggling.

Thanks so much for reading. If you'd like to connect with me outside of Dev here are my twitter and linkedin come say hi 😊.

Comments 24 total

  • Andrew Bone
    Andrew BoneFeb 15, 2024

    Can you think of any more powerful examples of how to use CSS functions?

  • Bernd Wechner
    Bernd WechnerFeb 16, 2024

    Can't wait to see the end of preprocessors.

    • Andrew Bone
      Andrew BoneFeb 16, 2024

      As CSS gets more and more features pre-processors seem less and less necessary, I imagine they'll hang around for a few years yet, even if they're no longer required.

  • cScarlson
    cScarlsonFeb 16, 2024

    You know that trick where you overlay a checkbox or radio button over a button's display so that the user interacts with the input instead of the button?... And how I have to recreate them all the time or use a JS component just to have one that's reusable?... Yeah, can't wait for mixins... This is going to be awesome!!!

  • Andrew Bone
    Andrew BoneFeb 16, 2024

    @afif I can already picture the day you start sharing your code, like you amazing triangles in this format 😅

    @function --gradient(--radius: 0px) {
      --gradient-part: calc(tan(60deg)*var(--radius)) top var(--radius), #000 98%, #0000 101%;
    
      @return 
        conic-gradient(from 150deg at 50% calc(3*var(--radius)/2 - 100%), #000 60deg, #0000 0) 0 0/100% calc(100% - 3*var(--radius)/2) no-repeat,
        radial-gradient(var(--radius) at 50% calc(100% - 2*var(--radius)), #000 98%, #0000 101%),
        radial-gradient(var(--radius) at left var(--gradient-part)),
        radial-gradient(var(--radius) at right var(--gradient-part));
    }
    
    @mixin --triangle(--radius: 0px, --width: 100px) {
      width: var(--width);
      aspect-ratio: 1/cos(30deg);
      -webkit-mask: --gradient(var(--radius));
      clip-path: polygon(50% 100%, 100% 0, 0 0);
      background: linear-gradient(45deg, #FA6900, #C02942);
    }
    
    .triangle {
      @apply --triangle(20px, 180px)
    }
    
    Enter fullscreen mode Exit fullscreen mode
    • Temani Afif
      Temani AfifFeb 16, 2024

      Not only for a specific shape but it would be a function that can return most of the shapes.

      Something like @mixin --triangle(--type: 1,--radius: 0px, --size: 100px) where type will define the nature of the triangle 😉

      • Andrew Bone
        Andrew BoneFeb 16, 2024

        Honestly I'm excited to see how far you can take it 😲

  • Eckehard
    EckehardFeb 16, 2024

    Rerferring to this source, W3Schools listed 228 CSS properties in 2020, some people counted 584, but the trend seems to be further upwards.

    Adding functions and mixins will make CSS more flexible, but seems to establish a second programming language in the browser. And you probably will still need some javascript too, as not all transitions refer to styling. Think of a content, that needs to change in sync with the styles.

    I´m not sure we should be too happy about this trend. Javascript has already more than enough (some people say: too many) features. Often you have far more than one way to do the same thing, which can be pretty confusing. Now we get more options to do the same things in CSS, that could been done in JS before. This blows up the whole balloon again...

    When will it burst?

    Image description

    • Andrew Bone
      Andrew BoneFeb 16, 2024

      A valid concern, where CSS has JS beat is with optimisations. Browser makers can take the strain of doing calculations off the developers hands and allow the calculations to be done off main thread.

      This can, of course, be abused and used wrong but less JS is always a good thing in my mind.

      • Eckehard
        EckehardFeb 16, 2024

        So we add functions and mixins to CSS to avoid Javascript? This is a strange logic. Balancing a tower of different and mostly incompatible tools certainly doesn't make things any easier.

        • Temani Afif
          Temani AfifFeb 16, 2024

          Why don't you see the problem differently like JavaScript developers using JS to emulate native CSS feature? This is not something new, it's like that since years. I cannot count the number of JS library used to animate stuff where a simple CSS keyframe will do the job.

          We don't want to avoid JS but we want JS developer to not avoid CSS and consider all the native feature that are already implemented instead of reinventing the wheel with a complex JS code.

          As side note, you don't need to know all the 584 CSS properties. No one need to know them. The fact that CSS is adding new features doesn't oblige you to use them.

          • Eckehard
            EckehardFeb 16, 2024

            Inventing a new language for every sentence you want to speak is not a good Idea...

            We see an inflation of new tools, each providing a "new syntax" as if there had been no other programming language ​​before (See my post about the different ways to write loops). And often, this "new" approaches are created with a limited target in mind and a fairly limited syntax, so they limit what you can do.

            Languages like C++, Pascal or Javascript are "general purpose" languages, they give you a small set of rules with which you can formulate all kinds of tasks. And they have a consistent syntax which is kind of battle tested.

            We have seen this so many times, an all the time it is claimed to make things "easier" for developers. Angular uses JSX, which is more or less simply HTML syntax, so, there is no option to write loops. So, Angular introduced a new command: ng-for

            <ul>
                <li *ngFor="let item of stuff">
                  {{item.title}}
                </li>
            </ul>
            
            Enter fullscreen mode Exit fullscreen mode

            The syntax is driven by what HTML provides, but it is far from "consistent". It is more a "dirty hack". This is not HTML, it is not Javascript, it is not a programming language at all. It is not even a template literal, which would be the common form to include variables into JSX. It is absolutely no wonder that you find thousands of pages just explaining how to use ng-for. I don´t think this is "easy" in any way.

            And what does ng-for provide? You just can loop through simple flat arrays, nothing else. What if you want to display every second element, reverse the order, your array is nested or whatever may happen to you? Come on, let introduce some new properties to handle all this special cases?

            It is true: Nobody needs 584 CSS properties. But If I have a special task, I need to find the right property. Nobody can remember 584 CSS properties, so it will take some time to find the right one. What if there are 5.840 CSS properties or 58.400?

            There is an infinite number of things people may want to do on a website. If we create a new property for every possible situation, we will need an infinite number of properties. I would prefer a "general purpose" language to do this job. And as browsers already understand Javascript, why use something else?

            • András Tóth
              András TóthFeb 17, 2024

              I partially agree with you. On ngFor and Angular absolutely. React version of JSX made a lot of sense instead, it is just better design. Same thing is true with yaml and helm: horrible new, mini languages, instead of supporting a couple of solid languages and passing your own JS object for example. You know what nobody considers mini programming languages? God damned command line interfaces, where there's an absolute chaos on if you just write -auth or -a or simply auth? Is -f file or is it force?

              So I get why exactly you are angry about it and why you are defensive.

              However, however! Mixins are amazing for composing reusable blocks of CSS. You can reduce the bundle one needs to download and also you can make things DRY by defining it once.

              I personally use mixins to describe the things that are not obvious in CSS.

              But where I disagree:

              • unlike my example with yaml kubernetes and infrastructure, in the browser it does matter when and how things are optimized. It means you won't be able ever to beat pure CSS and pure GPU acceleration. You just can't do it in JS.
              • therefore it makes sense for CSS to be able to do very limited things like variables, very simple input-output functions, very simple calculations
              • CSS is optimized also for bundle size as it is an exception-based language: meaning you have the most out of it if you create a terse set of core defaults and override those defaults when they are needed
              • there are excellent new courses in CSS that makes you familiar with the small subset of the right tools: I have read through this and got mostly enlightened: every-layout.dev/rudiments/boxes/ You also need to do an advanced CSS course somewhere and you will be top-notch (it is true that there are too many bad advice, bad documentation is out there)

              Where it sucks though: when you have variable and values YOU MUST PROVIDE ERROR MESSAGES the very least. With CSS variables I struggled with this. It must be solved.

              Finally, I think the whole things is a false-dichotomy and we don't look at the real problem:
              We clearly have a need for a unified, well-designed and performant language for the browser, essentially turning it into a sandboxed virtual machine. We can't have it because of Browser Wars.

              • Temani Afif
                Temani AfifFeb 17, 2024

                Sorry but it seems you are misunderstanding how CSS works. It doesn't work per property and we don't learn properties when working with CSS, we learn concepts. We learn "Flexbox" and "CSS grid" as a layout algorithm but we don't learn all the properties that comes with Flexbox and CSS Grid. You learn the concept of CSS Selector and Cascading but you don't have to know all the selectors by heart.

                CSS will keep adding more and more properties and features but you don't have to know them and use them. I work with CSS everyday and I probably use only 10% of it. You can build a website without knowing CSS grid, without using any math functions, without clipping/masking, without knowing gradients, etc

                Also CSS and JS are two different beast. No one is meant to replace the other. If you can do your job using JS then do it but If I am able to do the same using CSS then I will do. Why limiting people to one universal approach?

                And as browsers already understand Javascript, why use something else?

                Browsers also understand CSS so I can ask you the same question: Why using JS to write an algorithm that is natively implemented by browsers using CSS?

                • Eckehard
                  EckehardFeb 17, 2024

                  If they add an option to use Javascript inside CSS, this would be great. All the complicated rules where and when to apply one ore another rule could be wirtten ins a language most people would unterstand. But CSS functions are not Javascript, they are a new island created just for CSS. Why?

                  Assume I want to do some fancy stuff, let say I want the second sibling of the first element be green, but only on sundays. It would be easy to write such a rule in Javascript.

                  If I want to do this with CSS functions, I first need to learn the new syntax. May be there are some constrain? Does CSS konow the time?

                  My example could be more complex, let say I want to use the weather report of New York to change the appearance of my app? Javascript gives me all this without any new rules and tools.

                  • Andrew Bone
                    Andrew BoneFeb 17, 2024

                    The web is made up of three fundamentals

                    HTML - which is only the structure of the page

                    CSS - how the page looks

                    JS - adding interactivity, modifying the Dom and other logic.

                    JS is very powerful but is often the reason web pages run slow, writing good JS is hard.

                    For a long time people have used JS to fill in the gaps that traditionally CSS left but as CSS gets better that gap gets smaller leading to a better web for everyone.

                    The overlap is artificial, in an ideal world there would be little overlap between the functions of HTML, CSS and JS.

                    • Eckehard
                      EckehardFeb 17, 2024

                      Wo is using HTML to describe the structure of a web page? Is it 1980 again? People are using React today, and they have good reasons to do so. The "separation of concerns" is a phantom that never was true.

                      The capabilities of HTML to describe the structure of a document are very limited. You create a document by mixing some CSS-references into your document, but there is no level of abstraction. You do not describe the structure of a document, you are mixing HTML and CSS and hope the result looks good.

                      Systems like Bootstrap tried to add some level of abstraction and to provide some prebuilt elements you could use, but people found this was limiting them too much.

                      Today, they use Tailwind, which adds all the CSS right into HTML.

                      It that what you are talking about?

                      • Andrew Bone
                        Andrew BoneFeb 18, 2024

                        I'm unsure if you're being actively obtuse but I'll give you the benefit of the doubt, though I think this will be my last comment 😊.

                        React uses a virtual Dom in JS that react-dom turns into html. For anything to be displayed in the dom it must be HTML, which was invented in the 90s, or the browser can't understand it.

                        Whilst it is true that JSX is not HTML by the time a browser sees it, it is. This adds a lag, even though it's a small one, between the JSX being parsed and the HTML being rendered. It's because of this that things like RSC have been created to build and ship HTML rather than JSX where possibly to reduce that lag.

                        Tailwind is a set of CSS utility classes that can be added to elements and is a solution for DRY but can lead to longer build times and shipping more to the client then you need to again this causes lag but this time it is at download rather than execution.

                        Making it so there is less need to patch things with slow cumbersome JS is a win for the end user, it is also something a dev will have to learn but that it the difference between good Devs and great Devs, putting the end user above DX.

                        With all that said the web is, and always will be, backwards compatible if you're happy doing it the way you know no one will force you to change your ways, I'm looking forward to the changes and that's ok. It sounds like you're not looking forward to the changes and that's ok too.

                        Thanks for taking the time to comment. 😊

  • chovy
    chovyFeb 17, 2024

    we'll be using this asap at Profullstack and Primatejs

    • Andrew Bone
      Andrew BoneFeb 17, 2024

      Awesome! Now that all the browsers are evergreen adopting new features like this is so convenient.

  • Tab Atkins Jr.
    Tab Atkins Jr.Mar 26, 2025

    I was surprised to click into this topic and see it was talking about Sass functions/mixins, and not the native CSS ones in drafts.csswg.org/css-mixins/. (Currently only Functions, we're still thinking about mixins.) Chrome's implementation is nearly ready!

    • Tab Atkins Jr.
      Tab Atkins Jr.Mar 26, 2025

      Oh lolllll I misread the dates, this post was last year, so the lack of CSS Custom Functions is completely understandable since the spec didn't exist back then. ^_^

      • Andrew Bone
        Andrew BoneMar 27, 2025

        This post was originally a look at what they were and a nod to the fact there were some early discussions about adding them to the spec. I'm planning on doing a revisited post when the spec is finialised and both functions and mixins are shipped in at least one browser.

        I noticed Adam Argyle did a post yesterday talking about mixins being added to canary behind a flag so we're almost there!

Add comment