Creating a Custom CSS Framework with Sass Mixins: A Guide
Written on
Chapter 1: Introduction to Custom Sass Mixins
In a recent project I embarked on, I became fascinated with the concept of segmenting my application to enhance its performance, particularly when deployed on the blockchain. A significant goal was to reduce the number of CSS files incorporated into the project. During my exploration, I considered a class-based methodology reminiscent of Tailwind CSS. However, I opted not to use any external frameworks, as I prefer to avoid unnecessary dependencies unless absolutely necessary. I believed that this method would offer more flexibility in the future.
Ultimately, I developed a Sass mixin to create my own class-based framework, and I am thrilled to share this with you.
The collaborative project I worked on with my colleagues is named Oisy, an open-source Ethereum wallet hosted on the Internet Computer.
Overview
The mixin I created is designed to eliminate code redundancy while allowing for the creation of classes that facilitate responsive components. For example, the following code snippet demonstrates its use:
<div class="flex justify-center gap-1 mt-4">
<span class="text-blue">Hello</span>
<span class="text-cyclamen font-bold">World</span>
</div>
This snippet results in a webpage featuring a div container with a centered flex layout and a top margin. Within this container, two inline span elements are styled with distinct colors, with one displayed in bold.
While I could have declared these classes globally, I aimed to generate them using a Sass utility for all styling purposes.
Gotcha
This solution exceeded my expectations when I initially started coding it; however, it's crucial to note that it doesn't match the sophistication of dedicated CSS frameworks like Tailwind. Before we move forward, it's important to recognize two limitations:
- Responsiveness is constrained to a single breakpoint. Throughout the application development, UI rules consistently relied on one breakpoint. When I attempted to incorporate multiple breakpoints, the visual results were not always as anticipated. If your needs require multiple breakpoints, the mixin might need adjustments to selectors to address these scenarios.
- Many CSS frameworks include a postCss task that cleans up your bundle by removing any unused styles after compilation. This feature is not included in my implementation.
Media Queries
While not strictly necessary for the execution of this solution, I utilized another mixin employed across all our applications. In a file named _media.scss, I integrated the following code to simplify the application of breakpoint rules prior to executing the core of this tutorial:
$breakpoint-xsmall: 320px;
$breakpoint-small: 576px;
$breakpoint-medium: 768px;
$breakpoint-large: 1024px;
$breakpoint-extra-large: 1300px;
@mixin min-width($breakpoint) {
@if ($breakpoint == xsmall) {
@media (min-width: $breakpoint-xsmall) {
@content;}
} @else if ($breakpoint == small) {
@media (min-width: $breakpoint-small) {
@content;}
} @else if ($breakpoint == medium) {
@media (min-width: $breakpoint-medium) {
@content;}
} @else if ($breakpoint == large) {
@media (min-width: $breakpoint-large) {
@content;}
} @else if ($breakpoint == xlarge) {
@media (min-width: $breakpoint-extra-large) {
@content;}
} @else {
@error "UNKNOWN MEDIA BREAKPOINT #{$breakpoint}";}
}
As previously discussed in another blog post, I won't delve deeply into the snippet above. If you're interested in more details, please check my article titled “Sass Media Queries Mixins”.
Class Generation
Now, we arrive at the core of our solution. To create the responsive classes we've previously mentioned, I devised the following Sass mixin in a file I named _utilities.scss:
@use 'media';
@mixin generate($selector, $property, $value) {
.xs:#{$selector} {
@include media.min-width(xsmall) {
#{$property}: $value;}
}
.sm:#{$selector},
.sm:#{$selector}[class*='-'],
.sm:#{$selector}[class*='xs:'] {
@include media.min-width(small) {
#{$property}: $value;}
}
.md:#{$selector},
.md:#{$selector}[class*='-'],
.md:#{$selector}[class*='xs:'],
.md:#{$selector}[class*='sm:'] {
@include media.min-width(medium) {
#{$property}: $value;}
}
.lg:#{$selector},
.lg:#{$selector}[class*='-'],
.lg:#{$selector}[class*='xs:'],
.lg:#{$selector}[class*='sm:'],
.lg:#{$selector}[class*='md:'] {
@include media.min-width(large) {
#{$property}: $value;}
}
.xl:#{$selector},
.xl:#{$selector}[class*='-'],
.xl:#{$selector}[class*='xs:'],
.xl:#{$selector}[class*='sm:'],
.xl:#{$selector}[class*='md:'],
.xl:#{$selector}[class*='lg:'] {
@include media.min-width(xlarge) {
#{$property}: $value;}
}
.#{$selector} {
#{$property}: $value;}
}
This utility accepts three parameters:
- $selector: The class name used in the components.
- $property: The CSS property to set, such as display or font-weight.
- $value: The expected value of the style, for example, block or 700.
For each combination of parameters, the mixin produces globally defined CSS classes alongside their responsive counterparts. The classes are systematically arranged, starting with the smallest media query and progressing to the larger ones, followed by the global value.
For instance, when employing the generator with a selector called block and defining display as the property with block as the value, the generated output looks like this:
@media (min-width: 320px) {
.xs:block {
display: block;}
}
@media (min-width: 576px) {
.sm:block,
.sm:block[class*='-'],
.sm:block[class*='xs:'] {
display: block;}
}
@media (min-width: 768px) {
.md:block,
.md:block[class*='-'],
.md:block[class*='xs:'],
.md:block[class*='sm:'] {
display: block;}
}
@media (min-width: 1024px) {
.lg:block,
.lg:block[class*='-'],
.lg:block[class*='xs:'],
.lg:block[class*='sm:'],
.lg:block[class*='md:'] {
display: block;}
}
@media (min-width: 1300px) {
.xl:block,
.xl:block[class*='-'],
.xl:block[class*='xs:'],
.xl:block[class*='sm:'],
.xl:block[class*='md:'],
.xl:block[class*='lg:'] {
display: block;}
}
.block {
display: block;
}
Usage
The generator mixin is responsible for producing the classes but does not inherently know which classes need to be generated. Therefore, developers must still declare the specific classes they want to utilize in their application when implementing this solution.
I find it useful to compartmentalize different styles into dedicated Sass files, creating separate files for each style topic.
To illustrate this application, I’ll share the classes I've declared, which are used in the code snippet featured in the first chapter of this tutorial.
display.scss:
@use '../mixins/utilities';
$property: display;
@include utilities.generate(hidden, $property, none);
@include utilities.generate(block, $property, block);
@include utilities.generate(flex, $property, flex);
justify-content.scss:
@use '../mixins/utilities';
$property: justify-content;
@include utilities.generate(justify-start, $property, flex-start);
@include utilities.generate(justify-end, $property, flex-end);
@include utilities.generate(justify-center, $property, center);
@include utilities.generate(justify-between, $property, space-between);
gap.scss:
@use '../mixins/utilities';
$property: gap;
@include utilities.generate(gap-1, $property, var(--padding));
@include utilities.generate(gap-2, #{$property}, var(--padding-2x));
@include utilities.generate(gap-4, #{$property}, var(--padding-4x));
Note that in the application, I utilize global CSS variables, such as --padding set to 8px.
margin.scss:
@use '../mixins/utilities';
$property: margin;
@include utilities.generate(m-0, $property, 0);
@include utilities.generate(mx-0, #{$property}-left, 0);
@include utilities.generate(mx-0, #{$property}-right, 0);
@include utilities.generate(ml-0, #{$property}-left, 0);
@include utilities.generate(mr-0, #{$property}-right, 0);
@include utilities.generate(my-0, #{$property}-top, 0);
@include utilities.generate(my-0, #{$property}-bottom, 0);
@include utilities.generate(mt-0, #{$property}-top, 0);
@include utilities.generate(mb-0, #{$property}-bottom, 0);
color.scss:
@use '../mixins/utilities';
$property: color;
@include utilities.generate(text-blue, $property, #3b00b9);
@include utilities.generate(text-cyclamen, $property, #ea6c99);
As demonstrated, generating these class helpers using the mixin we’ve constructed is quite straightforward. This method not only simplifies class declarations but also condenses them into just a few lines of code.
For additional examples of usage, feel free to explore Oisy’s repository.
Conclusion
In conclusion, we've delved into the creation of a dynamic and responsive class generation system utilizing Sass mixins. This approach has fulfilled my initial goals and exploration. While I may be uncertain about reusing it in future projects, several valuable concepts within it are certainly worth revisiting.
I must confess, as a developer who typically doesn’t favor CSS class-based methodologies, I enjoyed the process of creating this and have developed a newfound appreciation for its application.
Thank you for reading! For more engaging coding content, consider following me on Twitter / X.
Explore Sass Mixins and learn to build your own CSS library with this tutorial focused on Mixins.
Get started with this introduction to Sass and learn how to create your own CSS library.