Skip to main content

Migrating to TailwindCSS, iteratively, in your Drupal theme

Published on

I'm a huge fan of TailwindCSS. After using it, I find writing media queries for responsive interfaces complicated instead of leveraging the responsive variants of Tailwind. Not to mention I can stop fretting over the naming of my classes to ensure I meet my own made-up SMACSS/BEM standard.

I am migrating my personal site to use Tailwind. The current version isn't anything fancy, it's mostly vanilla CSS, but I pass it through PostCSS for some cleanup via Gulp. 

My new setup: Tailwind with purging compiled via Laravel Mix

I have some things moved over to Tailwind right now and could preserve the existing styling while ripping some things out.

If you find this interesting!

How I added Tailwind & Laravel Mix to my theme

In my theme, I ran the following commands. I'm going to be brief here as all of these were gathered from the respectful tool's documentation.

cd web/themes/custom/mglaman
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npm install -D laravel-mix

npx tailwindcss init
touch webpack.mix.js

This installed my dependencies and provided the base configuration files.

Here are my NPM scripts

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "mix --production",
    "dev": "mix",
    "watch": "mix watch"
  },

Configuring Laravel Mix

Laravel Mix is awesome. If you have ever needed to configure Webpack manually, it can be a nightmare. Mix provides a wonderful API on top of Webpack to streamline frontend tooling.

My theme has three CSS files

  • main.css: the frontend stylesheet
  • ckeditor.css: a CKEditor stylesheet for my editor experience
  • toolbar.css: Fixes some toolbar funk when I'm logged in

I pass these to the postCss API function, with some additional plugins for main.css, so that it is processed via Tailwind.

let mix = require('laravel-mix');

mix
.disableSuccessNotifications()
.postCss('./src/ckeditor.css', 'build')
.postCss('./src/main.css', 'build', [
  require('tailwindcss'),
  require('autoprefixer'),
])
.postCss('./src/toolbar.css', 'build')
.browserSync({
  proxy: 'mglaman.dev.ddev.site:80',
  files: [
    'build/main.css',
    'templates/**/*',
    'mglaman.theme',
  ]
})

Oh, one cool thing. That browserSync API method? Whenever my main.css, Twig templates, or theme extension file is modified, the browser will refresh. The proxy allows localhost:3000 to render my local Drupal site properly.

The first time you run Mix in watch mode, it'll install the necessary dependencies, and then you re-run the command.

Configuring Tailwind (and for optimal backward compatibility)

Now. This is the big part. A raw dump of Tailwind is 3.4MB or so. That's because Tailwind is verbose by default and assumes you will leverage purging of unused CSS declarations. I forgot to do a before and after, but my main.css is 10.4KB. It actually went down in size a small fraction once I added Tailwind and removed some existing CSS.

One problem can occur when you add Tailwind to an existing codebase: it includes a preflight that runs CSS normalization. My current site has no normalization, so it broke all the things. But, it's also important to note as you may already have something like this and now making a bigger CSS file.

The good news? You can disable the preflight.

Here's my Tailwind config, which performs purging based on class usages in my theme's Twig templates (note, this doesn't handle adding classes in my theme extension file, aka mglaman.theme)

module.exports = {
  corePlugins: {
    // @todo maybe enable when ready for bigger re-theme.`
    preflight: false,
  },
  purge: {
    content: [
      './templates/**/*.html.twig',
      './js/**/*.js',
    ],
    options: {
      keyframes: true,
    },
  },
  darkMode: 'media',
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
    plugins: [],
}

I also turned on Tailwind's dark mode. I have really basic dark mode support. But I wanted to preserve it. 

My header links now have the following CSS classes versus their own specific styling declarations:

text-gray-700 dark:text-gray-100

SUCCESS!

And with that, I have my theme ready to be converted over to Tailwind. I'm working on the way to highlight my live streams via my website. That means I needed to bust out some styling and groaned at having to write regular CSS again.

As I review my CSS, I spin out properties into their appropriate Tailwind classes and then remove it from the legacy CSS file.

-<div class="site">
-  <div class="site--header__wrapper">
+<div>
+  <div>
     <header class="site--header max-w-screen-lg flex flex-col lg:flex-row items-center py-2 lg:justify-between mx-auto">
-      <h1><a class="h-card no-underline hover:underline" rel="me" href="/">matt glaman</a></h1>
+      <h1><a class="h-card no-underline hover:underline text-gray-700 dark:text-gray-100" rel="me" href="/">matt glaman</a></h1>
       {{ page.primary_menu }}
       {{ page.secondary_menu }}
     </header>

🥳

I'm available for one-on-one consulting calls – click here to book a meeting with me 🗓️