Laravel News Meets Tailwind CSS
You probably haven’t noticed yet, but the Laravel News website just got a fresh coat of paint. We just launched a full one-to-one CSS refactor using Tailwind CSS under the hood! The goal of the project was to make it easier to implement design changes and add new content to the site.
What is Tailwind CSS?
Tailwind CSS is a utility-first CSS framework built by Adam Wathan, Jonathan Reinink, David Hemphill, and Steve Schoger. It takes everything we thought we knew about writing organized, maintainable CSS and throws it out. Tailwind uses PostCSS to generate thousands of utility classes that you can then use to style your HTML directly in the markup. This allows for a speedy development process and very little custom written CSS.
Upgrading from Laravel Elixir to Laravel Mix
To get this project started, I began by upgrading the codebase from Laravel Elixir to Laravel Mix. Mix has many benefits over Elixir, some of which are: – Uses Webpack under the hood – Easier to configure – The currently supported version
Upgrading to Mix was a straightforward process. Elixir was only compiling the Sass, combining some JavaScript files, and versioning the main JavaScript and CSS files. Pretty much all I had to do was install Mix via npm, update the scripts
section in the package.json, and change the configuration from this:
1// gulpfile.js 2 3var elixir = require('laravel-elixir'); 4 5elixir(function(mix) { 6 mix 7 .sass('all.scss', 'public/assets/css/all.css') 8 .scripts( 9 [10 // ... script files11 ],12 'public/assets/js/app.js'13 )14 .version(['/assets/css/all.css', 'assets/js/app.js']);15});
to this:
1// webpack.mix.js 2 3const mix = require('laravel-mix'); 4 5mix 6 .sass('resources/assets/sass/all.scss', 'public/assets/css/all.css') 7 .scripts( 8 [ 9 // ... script files10 ],11 'public/assets/js/app.js'12 );13 14if (mix.inProduction()) {15 mix.sourceMaps().version();16}
Setting Up Tailwind
The next step in the process was setting up Tailwind. To get started, I had to install Tailwind via npm and make a few changes to the Laravel Mix configuration. After installing Tailwind, I added the following code to my webpack.mix.js
file.
1mix2 .sass('resources/assets/scss/tailwind.scss', 'public/assets/css')3 .options({4 postCss: [tailwindcss('./tailwind.js')]5 });
Next, I created the tailwind.css
file with the following contents:
1@tailwind preflight;2 3@tailwind components;4 5@tailwind utilities;
Tailwind offers great customizability via its configuration file. To generate the default configuration file, just run ./node_modules/bin/tailwind init tailwind.js
, which will create a tailwind.js
file at the root of your project. Here, you can configure everything from colors to margin and padding sizes and even responsive breakpoints.
A shoutout to the team over at Zaengle, for leaving me with a phenomenal Sass codebase. It was based on a lightweight Sass framework called Bourbon and very well organized. At this point, it was straightforward for me to replace the default Tailwind configuration with values I found in the Sass variables. I quickly copied over font sizes, colors, and spacing sizes to the tailwind.js
file, added a few basic default styles for headings and paragraphs to the tailwind.scss
, and then I was ready to go!
Converting Pages / Extracting Reusable Classes
To begin the real work, I started by replacing the CSS file that was initially being loaded with the new tailwind.css
file being compiled by Laravel Mix. This removed all the styling, so I was starting from scratch. Then, I added some basic classes I knew I would need to the body tag, bg-off-white font-sans text-base text-grey-darker leading-loose
. Then I moved on to the navigation. Using the same HTML for mobile and desktop layouts has always been a little difficult to get right, and there’s not much Tailwind can do for you here. It took me a while to get the navigation sections converted over, and I had to write a little custom CSS to get it perfect. Tailwind doesn’t currently have a way to target pseudo-selectors, so I had to add the following to my tailwind.scss
file:
1@screen md {2 .nav-container::after {3 content: "";4 height: 2px;5 top: 66px;6 @apply .absolute .w-full .pin-l .bg-off-white;7 }8}
Now, you might notice some weird syntax mixed in with that CSS. The @apply
and @screen
directives are a couple features Tailwind includes to help when you end up writing custom CSS. You can read more about Tailwind’s custom directives here. @apply
lets you “apply” Tailwind’s utility classes to your custom classes so you can reuse the colors and sizes in your Tailwind config file. This keeps your design nice and consistent across the site. The @screen
directive compiles down to a regular media query using the width you pass as an argument. That code example compiles to the following:
1@media (min-width: 750px) { 2 .nav-container::after { 3 content: ""; 4 height: 2px; 5 top: 66px; 6 position: absolute; 7 width: 100%; 8 left: 0; 9 background-color: #f5f5f5;10 }11}
After finishing the navigation, I moved on to the rest of the homepage. I went through each section and redesigned the markup to match the old design as closely as possible. Whenever I had trouble getting something exactly right, I would source-dive the old code and see how it was done before.
As I was going through each page, I started noticing certain sets of classes I was using over and over. The cards on the homepage are a good example of this. After repeating those classes a couple of times, I decided to extract components for them in my custom CSS. Using Tailwind, extracting components is easy. Just copy the classes that were applied to an element and use the @apply
directive to apply all those classes to a single class like .card
. Here are a few examples of custom classes I created:
1.card { 2 margin: 15px 0; 3 width: calc(100% - 30px); 4 @apply .text-white .bg-white .rounded-sm .overflow-visible .shadow-sm .self-stretch; 5} 6 7.card .card__image { 8 max-height: 150px; 9 @apply .flex .items-start .justify-center .rounded-t-sm .overflow-hidden .bg-grey-lighter;10}11 12.card .card__image img {13 @apply .w-full .flex-1 .block .rounded-t-sm .transition;14}15 16.card:hover .card__image img {17 @apply .opacity-75;18}
When I got to the blog pages, I had to change up the workflow a bit. Since the post content is loaded from the database, I couldn’t apply the utility classes directly to the content. I had to use custom classes and the @apply
directive to style post content. I ended up with something like this:
1.post__content h1 { 2 @apply .text-4xl .leading-tight; 3} 4 5.post__content h2 { 6 @apply .text-2xl .leading-tight; 7} 8 9.post__content h3 {10 @apply .text-lg .leading-tight;11}12 13.post__content ul, .post__content ol {14 @apply .mb-8;15}16 17.post__content p {18 @apply .mb-8;19}20 21...
The further I got into the code refactor, the easier it got. The design is remarkably consistent across the board, and many of the sections are utilizing Blade partials, so thankfully I didn’t have to refactor those every time.
Cleaning Up and Optimization
At the end, I got to do the most fun part: deleting code! I ended up removing almost 200 Sass files from the project (Bourbon included) with only 12 small Sass partials remaining. These were mostly reusable components I had created for classes I was repeatedly using. With some small JavaScript refactors due to some unneeded dependencies, I was also able to reduce the JavaScript bundle size pretty drastically (goodbye jQuery)!
One of the big issues some people notice with Tailwind compared to other frameworks is the default bundle size when compiled. Out of the box, the compiled CSS is almost 49kb! But don’t worry, there are a few options for controlling the file size which you can read more about here.
For this project, PurgeCSS was the perfect option to reduce the file size. The Tailwind docs include documentation explaining how to configure PurgeCSS to work with Tailwind which I would recommend reading. So what does PurgeCSS do? You add it as a build step and when you compile your CSS for production, it scans all your view files and any JavaScript components you may have for CSS classes then compares those to the selectors in your compiled CSS. It then removes any CSS that isn’t being used, which in my experience has worked wonderfully.
Below are the before/after file sizes: – CSS: ~17.1kb gzipped (before) / ~6.8kb gzipped (after) – JS: ~54.8kb gzipped (before) / ~13.2kb gzipped (after)
One last PurgeCSS tip: when loading content from a database or an API, make sure to whitelist any classes used in that content. Learn more about ways to do that here.
Wrap-up
Overall, this was a really fun project to work on, and I’m glad to have been a part of it. If you have any questions about any of the tools mentioned in this post or about the development process, feel free to contact me, Jason Beggs!