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.
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));
}
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%);
}
}
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)
}
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 ๐.
Top comments (20)
Can't wait to see the end of preprocessors.
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.
@afif I can already picture the day you start sharing your code, like you amazing triangles in this format ๐
Stop using obsolete methods to create CSS Triangles! โ ๏ธ
Temani Afif ใป Feb 13
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)
wheretype
will define the nature of the triangle ๐Honestly I'm excited to see how far you can take it ๐ฒ
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!!!
we'll be using this asap at Profullstack and Primatejs
Awesome! Now that all the browsers are evergreen adopting new features like this is so convenient.
Can you think of any more powerful examples of how to use CSS functions?
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?
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.
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.
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.
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
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?
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 withyaml
andhelm
: 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 simplyauth
? 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:
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.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 neededWhere 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.
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?
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?
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.
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.
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?
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. ๐
Some comments have been hidden by the post's author - find out more