How I made my own CSS framework akin to TailwindCSS?
Making a utility-based CSS framework
What is tailwind?
It is a utility first framework. It provides thousands of utility classes that can be used together to build complex components, directly in your markup. You'll rarely end up writing actual CSS.
Why make my own framework?
I decided to make my own framework to gain a better understanding of how it all works and test my CSS skills. I also used it in some of my react projects to test its usability, check this project where I ended up using it.
How it works?
In Utility-based CSS frameworks, you style elements by applying pre-existing classes directly in your HTML. Check below example card that is made with MintUI The above card can be created with a bunch of useful utility classes as shown in the below code snippet. No need to write any CSS.
<div class="displayFlex p2 bgWhite rounded border borderGray2 shadowMdGray2">
<img src="./joey.jpg" class="w24 roundedFull mr3" alt="user"/>
<div class="displayFlex flexCol">
<p class='fontBold lineHeightMd'>Joey Tribbiani</p>
<small class='mb1 fontSemiBold'>Actor</small>
<em class="mb2 textXs textGray5">Previously: Cologne sampler, Acting Teacher, Waiter</em>
<small class=''>Email: joeytribbiani@joey.com</small>
</div>
</div>
Let's get started
Before starting out I'd like to point out some important points to keep in mind when making a utility class framework:
- Make sure each utility class does only one thing.
- Class names should be explicit and follow some convention whereby it's easier to predict other classes. Example: I followed camelCase to name classes (displayFlex, textSm, borderNone, etc).
- use a CSS pre-processor like SASS which allows you to use loops so you can easily generate classes.
With that out of the way, let's start: Create a main.scss file - this file will be compiled to CSS in the end. Import any partials (which I'll create later) in this file so that it all compiles together and you get a single CSS file with all classes.
- Reset browser defaults
The goal here is to reduce cross-browser inconsistencies in things like box-sizing, margins and font sizes, and so on.
/*main.scss*/ *, *::after, *::before{ margin: 0; padding: 0; box-sizing: border-box; } html{ font-size: 16px; /* reduce base fontsize for mobile*/ @media(max-width: 767px){ font-size: 14px; } }
Come up with your color palette : You need to decide on all the colors that you will provide in your framework. These colors can be used to generate classes to color the background, text, border, etc. If you are going to add shades of a particular color, it's better to differentiate between two shade classes by some numeric value.
Example: In MintUI I've added 10 colors with each color having 5 shades. See the below image for reference.// _colors.scss (a sass partial file) // all colors in this sass map, loop through it later to generate classes $colors: ( White: white, Black: black, Transparent: transparent, Gray1: #f9fafb, Gray2: #e4e7eb, Gray3: #d1d5db, Gray4: #64748b, Gray5: #334155 Red1: #fef2f2, Red2: #fecaca, .... );
Create classes based on your colors : Once you have your color palette in place, you can start creating classes for background, text color, border color, etc based on this palette. Below you can see I'm using
@each
of SASS to map key value pairs to generate classes.// colors is a map which contains key:value pairs @each $colorName, $colorValue in $colors { // this will generate classes like bgGray2, bgBlue4, etc .bg#{$colorName}{ background-color: $colorValue; } // generate border color classes like borderOrange3, borderPurple1, etc .border#{$colorName}{ border-color: $colorValue; } // generate text color classes like textBlack, textGreen5, etc .text#{$colorName}{ color: $colorValue; } }
- Add spacing units and margin/padding classes:
- Add all the units of spacing that you'd like to support. I've added 24 units for spacing where each unit would represent 0.25rem i.e. my spacing scale ranges from 0rem to 6rem. You can change this scale depending on your use case. The more units you have, the more classes will be created.
- To create classes for each side i.e. margin-top or padding-bottom you'll need to map sides as well.
// _spacing.scss $spacers : ( 0,1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16, 17,18,19,20, 21,22,23,24 ); $sides: ( 'Top','Right','Bottom','Left' ); // generate margin and padding classes @each $space in $spacers{ // for each side @each $side in $sides { // creates classes like mRight6, mLeft1, etc .m#{$side}#{$space} { margin-#{$side}: #{$space/4}rem; } // creates classes like pTop4, pBottom3, etc .p#{$side}#{$space} { padding-#{$side}: #{$space/4}rem; } } }
Add grid layout support : I've added a 12 column grid - which is the standard across multiple CSS frameworks. To use grids you'll have to use
displayGrid
class to declare the display property as grid and then specify the number of columns using one of thegridCols*
classes. You can add more classes depending on your use case.// main.scss .displayGrid{ display:grid; } // grid column list $gridColumns : ( 1,2,3,4,5,6,7,8,9,10,11,12 ); // grid-template-columns to divide grid into number of columns @each $cols in $gridColumns { .gridCols#{$cols} { grid-template-columns: repeat($cols, minmax(0, 1fr)); } // span a div across multiple columns .gridColSpan#{$cols} { grid-column-start: span $cols; } }
Add flex layout support : Adding flex layout support is easier than grids as you only need to add a few classes. To make an element flex, use
displayFlex
class. Add other specific classes to adjust the behavior of flex accordingly.// main.scss .displayFlex{ display: flex; } .flexWrap{ flex-wrap: wrap; } .flexCol{ flex-direction: column; } .flexGrow{ flex-grow: 1; } // use to align content inside flex container .justifyBetween{ justify-content: space-between; } .justifyCenter{ justify-content: center; } .itemsCenter{ align-items: center; }
Add support for responsive breakpoints : To add a utility that will only take effect at a certain breakpoint, you'll need to add responsive variants of that utility. So when you have a responsive variant added along with a normal one, at the specified breakpoint the responsive variant will take priority and apply those styles.
In MintUI I've only added a single breakpoint(md) at 768px, but here for a better explanation, I'll add 2 breakpoints(one at 768px and another at 1024px) - md and lg respectively.// main.scss - where you have specified grid utilities ... $gridColumns : ( 1,2,3,4,5,6,7,8,9,10,11,12 ); @each $cols in $gridColumns { .gridCols#{$cols} { grid-template-columns: repeat($cols, minmax(0, 1fr)); } // responsive variants below // creates classes like md:gridCols3, md:gridCols5, etc .md\:gridCols#{$cols} { @media(min-width: 768px){ grid-template-columns: repeat($cols, minmax(0, 1fr)) !important; } } // creates classes like lg:gridCols2, lg:gridCols3, etc .lg\:gridCols#{$cols} { @media(min-width: 1024px){ grid-template-columns: repeat($cols, minmax(0, 1fr)) !important; } } } ...
And in HTML you can chain these classes to achieve a different grid layout at different breakpoints. For example: below you can see that
<main>
has a grid layout with 1 column when no breakpoint is specified, 2 columns at md and 3 columns at lg breakpoint.<main class="displayGrid gridCols1 md:gridCols2 lg:gridCols3" > ... ... </main>
Add other utility classes: You can add more utility classes related to font-size, font-weight, height, width, text-alignment, etc. It depends on how comprehensive you want to be.
Compile to a single CSS file: So far we have 3 different SASS files -
main.scss
and two partial files(_colors.scss
and_spacing.scss
). We need to import those partials into main.scss and compile this file to CSS. The compiled CSS file will have all the generated classes. If you don't know how to compile scss to css, you can use this vscode extension to do it or follow this article.// main.scss // import those partials which are in a directory called _partials // also no need to add _(underscore) to partial filename while importing @import '_partials/colors'; @import '_partials/spacing'; //... other scss here
Limitations?
I have created most of the basic classes needed for a utility-based framework but there's still a lot of use cases that I haven't covered. For example, more breakpoints can be added, hover/focus states can be added as well, purging of unused CSS isn't available, etc.
You can check out the project at MintUI
You can reach out to me at my Twitter or my Linkedin