Laravel News Meets Tailwind CSS

Published on by

Laravel News Meets Tailwind CSS image

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:

// gulpfile.js
 
var elixir = require('laravel-elixir');
 
elixir(function(mix) {
mix
.sass('all.scss', 'public/assets/css/all.css')
.scripts(
[
// ... script files
],
'public/assets/js/app.js'
)
.version(['/assets/css/all.css', 'assets/js/app.js']);
});

to this:

// webpack.mix.js
 
const mix = require('laravel-mix');
 
mix
.sass('resources/assets/sass/all.scss', 'public/assets/css/all.css')
.scripts(
[
// ... script files
],
'public/assets/js/app.js'
);
 
if (mix.inProduction()) {
mix.sourceMaps().version();
}

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.

mix
.sass('resources/assets/scss/tailwind.scss', 'public/assets/css')
.options({
postCss: [tailwindcss('./tailwind.js')]
});

Next, I created the tailwind.css file with the following contents:

@tailwind preflight;
 
@tailwind components;
 
@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:

@screen md {
.nav-container::after {
content: "";
height: 2px;
top: 66px;
@apply .absolute .w-full .pin-l .bg-off-white;
}
}

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:

@media (min-width: 750px) {
.nav-container::after {
content: "";
height: 2px;
top: 66px;
position: absolute;
width: 100%;
left: 0;
background-color: #f5f5f5;
}
}

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:

.card {
margin: 15px 0;
width: calc(100% - 30px);
@apply .text-white .bg-white .rounded-sm .overflow-visible .shadow-sm .self-stretch;
}
 
.card .card__image {
max-height: 150px;
@apply .flex .items-start .justify-center .rounded-t-sm .overflow-hidden .bg-grey-lighter;
}
 
.card .card__image img {
@apply .w-full .flex-1 .block .rounded-t-sm .transition;
}
 
.card:hover .card__image img {
@apply .opacity-75;
}

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:

.post__content h1 {
@apply .text-4xl .leading-tight;
}
 
.post__content h2 {
@apply .text-2xl .leading-tight;
}
 
.post__content h3 {
@apply .text-lg .leading-tight;
}
 
.post__content ul, .post__content ol {
@apply .mb-8;
}
 
.post__content p {
@apply .mb-8;
}
 
...

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!

Jason Beggs photo

TALL stack (Tailwind CSS, Alpine.js, Laravel, and Livewire) consultant and owner of roasted.dev.

Filed in:
Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Visit Laravel Forge
Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
LoadForge logo

LoadForge

Easy, affordable load testing and stress tests for websites, APIs and databases.

LoadForge
Paragraph logo

Paragraph

Manage your Laravel app as if it was a CMS – edit any text on any page or in any email without touching Blade or language files.

Paragraph
Lucky Media logo

Lucky Media

Bespoke software solutions built for your business. We ♥ Laravel

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
DocuWriter.ai logo

DocuWriter.ai

Save hours of manually writing Code Documentation, Comments & DocBlocks, Test suites and Refactoring.

DocuWriter.ai
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector

The latest

View all →
Generate Code Coverage in Laravel With PCOV image

Generate Code Coverage in Laravel With PCOV

Read article
Non-backed Enums in Database Queries and a withSchedule() bootstrap method in Laravel 11.1 image

Non-backed Enums in Database Queries and a withSchedule() bootstrap method in Laravel 11.1

Read article
Laravel Pint --bail Flag image

Laravel Pint --bail Flag

Read article
The Laravel Worldwide Meetup is Today image

The Laravel Worldwide Meetup is Today

Read article
Cache Routes with Cloudflare in Laravel image

Cache Routes with Cloudflare in Laravel

Read article
Learn how to manage timezones in your Laravel Apps image

Learn how to manage timezones in your Laravel Apps

Read article