Take your Drupal theming to the next level using a modern tech stack that combines the power of Tailwind CSS, LiveReload, and SCSS. This is a hands-on guide to setting up a Drupal theme with hot reloading, and leveraging both Tailwind CSS and SCSS for a streamlined development workflow.

Introduction

In Drupal front-end development, efficiency and speed are key. With the rise of utility-first CSS frameworks like Tailwind CSS, developers have access to an incredibly fast and responsive styling process. However, there’s a need for balance - while Tailwind CSS offers immense productivity, there are times when the simplicity of overriding styles with a few SCSS rules can be more effective than rewriting endless templates. That’s where this guide comes in.

I’ve found Tailwind to be extremely helpful in my work, but I’m not dogmatic about using it exclusively. In a Drupal context, there are instances when it’s more straightforward to use a bit of custom SCSS. My goal is to combine the best of both worlds - the rapid development speed of Tailwind with the flexibility and power of SCSS. To achieve this, I’ve set up a development environment that uses Gulp for task automation, enabling hot reloading (via LiveReload) for a more dynamic and immediate development experience.

One of the goals of this setup is to allow writing to multiple target CSS files, facilitating the use of granular Drupal libraries. This approach maintains the modular and performant nature of Drupal theming, ensuring that you only load what you need, when you need it.

Finally, the integration of LiveReload in our development setup is a game-changer, allowing us to see changes in real-time as we code. This immediate feedback loop really accelerates the theming process, making it more efficient and fun.

Outline

  1. Prerequisites

    • Install Drupal
    • Set up a new Drupal theme
    • Install Node.js and NPM
  2. Integrate Tailwind CSS

    • Installing Tailwind CSS, PostCSS
  3. LiveReload for Instant Feedback

    • Configuring LiveReload
    • Streamlining Your Workflow
  4. Setting up SCSS

    • Gulp
    • Organizing Your Stylesheets
    • Gulp Tasks for Multiple Outputs
    • Integrating with Drupal Libraries
  5. Tips and Best Practices

    • Balancing Tailwind and Custom SCSS
    • Optimizing for Performance

1 .Prerequisites

Before diving into the specifics of integrating LiveReload, SCSS, and Tailwind CSS with Drupal, there are a few prerequisites you’ll need to have in place. This guide assumes you’re already familiar with the basics of setting up a Drupal site and a custom theme. Here’s what you need to get started:

  • Install Drupal: Make sure you have a Drupal site up and running. We’re working with Drupal 10 at the time of writing this guide.

  • Set Up a New Drupal Theme: You should have a new Drupal theme ready for customization. In this guide, we’re extending a theme from the stable9 base theme, which is a common starting point for Drupal 10 theming.

  • Install Node.js and NPM: Node.js and NPM are essential for managing the JavaScript and CSS tooling. Ensure you have them installed on your development machine.

For detailed guidance on installing Drupal, setting up a new theme, and installing Node.js and NPM, there are numerous resources and tutorials available online. These steps are fairly standard and well-documented in the Drupal community and beyond.

Once you have these prerequisites in place, you’ll be ready to start with this guide!

2. Integrating Tailwind CSS

Tailwind CSS has revolutionized how we approach styling in web development with its utility-first approach. Integrating it into your Drupal theme can accellerate your development process, and more importantly result in a more maintainable theme. Once you get the hang of using it, it’s really powerful because you end up with way less css bloat, and your theme becomes more maintainable because you don’t have the problem of css overrides piling up on each other and becoming impossible to refactor. Using utility classes lets you deal with layout directly in your templates so you’re not constantly swithing between css and .html.twig.

But this isn’t a post about how great Tailwind is. Let’s assume you’re on board, and go through how to install Tailwind CSS and set up PostCSS for your Drupal theme.

Installing Tailwind CSS, PostCSS

To begin, you’ll need to install some packages in your theme directory. Navigate to your theme’s root folder in your terminal, and install these dev dependencies:

npm install tailwindcss postcss autoprefixer --save-dev

PostCSS is a tool for transforming CSS with JavaScript plugins, and it processes Tailwind’s directives. With PostCSS installed, create a configuration file named postcss.config.js in your theme’s root directory.

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

This configuration includes Tailwind CSS and Autoprefixer. Autoprefixer automatically adds vendor prefixes to your CSS, ensuring compatibility across different browsers.

Now, create a Tailwind configuration file using the Tailwind CLI:

npx tailwindcss init

This command generates a tailwind.config.js file which we’ll customize to look like this:

module.exports = {
  content: [
    './templates/**/*.html.twig',
    './js/**/*.js',
    // Add other paths that might contain class names.
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

This file tells Tailwind which files to watch to build the set of utility classes taht your project needs, no more no less than what you’re actually using. Efficient!

Finally, you’ll need to include Tailwind in your CSS. Create an entry CSS file src/tailwind.css and add the following Tailwind directives:

@tailwind base;
@tailwind components;
@tailwind utilities;

These directives are placeholders that Tailwind uses to inject its base styles, components, and utilities into your final stylesheet. Your code editor may not like the look of those @tailwind directives, but it will be fine.

Now let’s add some scripts to package.json:

"scripts": {
    "build:css": "postcss src/tailwind.css -o css/tailwind.css",
    "watch:css": "postcss src/tailwind.css -o css/tailwind.css --watch",
  },

Make sure you’re including the output tailwind css in you theme’s global css dependencies. Your mytheme.libraries.yml should look something have something this:

# Main theme library.
global:
  css:
    base:
      css/tailwind.css: {}

Flush the Drupal cache, and then you can try kicking off Tailwind on the command line:

npm run watch

Experiment by adding a few utility classes in a template and refreshing the page to see if it works!

Now we have a setup where your templates are being parsed to see which utility classes they are using, and then Tailwind cobbles them all together into the output css/tailwind.css which your Drupal theme includes on each page, because it’s included globally. When you have caching turned on, this file will be concatenated and minified with other css that’s needed for the page.

With Tailwind CSS installed and PostCSS configured, you’re now ready to use utility-first styling in your Drupal theme! This is a really good start and you could already stop here, but there’s more! How nice would it be to not have to reload the page every time you edit a file?

In the next sections, we’ll get you cooking with hot reloading using LiveReload and then we’ll cover setting up SCSS for efficient theming workflow.

3. LiveReload for Instant Feedback

LiveReload is a tool that enhances your development workflow by automatically reloading your web page when files change. This feature is really useful during frontend theme development as it provides instant feedback on your changes.

Configuring LiveReload

To use LiveReload, you first need to install the LiveReload server in your theme’s development environment.

npm install livereload --save-dev

Once installed, you can start the LiveReload server with the following command:

npx livereload --exclusions node_modules/ --exts 'css,js,twig,theme,apng,avif,gif,jpg,jpeg,jfif,pjpeg,pjp,png,svg,webp'

We’re going to add another package so we can run that script at the same time as postcss from above

npm install concurrently --save-dev

Now let’s add a couple scripts to our package.json


"scripts": {
    "build:css": "postcss src/tailwind.css -o css/tailwind.css",
    "watch:css": "postcss src/tailwind.css -o css/tailwind.css --watch",
    "livereload": "livereload --exclusions node_modules/ --exts 'css,js,twig,theme,apng,avif,gif,jpg,jpeg,jfif,pjpeg,pjp,png,svg,webp'",
    "start": "concurrently \"npm run watch:css\" \"npm run livereload\""
  },

Now from the command line we can run LiveReload and Postcss

npm run start

Now before this actually works you need to set up the client/browser so that it actually receives the updates from the LiveReload server. By default LiveReload sends updates over port 35729 (using a WebSocket), and the client needs to listen to this in order to know what to do. The typical way to do this is to install a LiveReload browser extension, but I like to avoid installing extensions when I can, so I did it by simply adding this line in the <head> of my html.html.twig template:

<script src="http://localhost:35729/livereload.js"></script>

(@todo switch this off when not on dev environment)

Now reload your page, and then try changing something in one of your templates or stylesheets - if everything went well, you should see the update show up without you having to reload the page in your browser. Nice!

So now we have Tailwind and hot-reloading set up, which is already amazing for rapid theme development. But what if you could take it to the next level and write SCSS instead of grandpa’s clunky old plain CSS?

(…To be continued!)

4. Setting up SCSS

SCSS, (aka Sassy CSS), is an extension of CSS that allows you to use variables, nested rules, mixins, functions, and more. It’s a great way to write more maintainable and modular CSS. A great feature of SCSS syntax is that it extends CSS, so CSS is valid SCSS. This is handy because it means you can simply rename a preexisting .css file as .scss and then begin using it using the SCSS extensions.

In this chapter, we’ll go through how to set up SCSS in your Drupal theme and how to organize your stylesheets effectively.

Gulp

We’ll be using Gulp as a taskrunner to help with transpiling all our scss over to css, so we’ll need to install some more dev dependencies:

npm install gulp gulp-sass gulp-postcss gulp-sourcemaps gulp-rename --save-dev

Organizing Your Stylesheets

The idea is that we are going to write SCSS, but since the browser can’t parse SCSS, we need to get it transpiled to CSS. We will set up a directory stucture that parallels the target css directory. Here, we will construct a /src directory and within it we can place all our SCSS using subfolders that parallel the target /css folder that contains css files that are referenced in our theme’s .libraries.yml Here’s an example structure for all our SCSS files:

your-theme/
|-- src/
   |-- base/
      |-- variables.scss
      |-- typography.scss
   |-- components/
      |-- buttons.scss
      |-- cards.scss
   |-- layout/
      |-- header.scss
      |-- footer.scss
      |-- main.scss
   |-- styles.scss

Gulp Tasks for Multiple Outputs

Next, we’ll need to set up a gulpfile.js in our theme’s root with the following:

const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const sourcemaps = require('gulp-sourcemaps');
const rename = require('gulp-rename');

function styles() {
    return gulp.src('src/**/*.scss')
        .pipe(sourcemaps.init())
        .pipe(sass().on('error', sass.logError))
        .pipe(postcss([autoprefixer()]))
        .pipe(sourcemaps.write('.'))
        .pipe(rename((path) => {
          // Replace 'src' with 'css' in the directory path
          path.dirname = path.dirname.replace(/^src/, 'css');
        }))
        .pipe(gulp.dest('css'));
}

function watch() {
    return gulp.watch('src/**/*.scss', styles);
}

exports.styles = styles;
exports.watch = watch;

These tasks look at the src folder and whenever an .scss gets updated, it transpiles it to a .css in the theme’s target css folder.

Next we need to add some scripts to our package.json, and update some of the existing scripts:

"scripts": {
    "build:css": "postcss src/tailwind.css -o css/tailwind.css",
    "watch:css": "postcss src/tailwind.css -o css/tailwind.css --watch",
    "livereload": "livereload --exclusions node_modules/ --exts 'css,js,twig,theme,apng,avif,gif,jpg,jpeg,jfif,pjpeg,pjp,png,svg,webp'",
    "gulp-watch:css": "gulp watch",
    "watch": "concurrently \"npm run watch:css\" \"npm run livereload\" \"npm run gulp-watch:css\"",
    "build": "gulp styles & postcss src/tailwind.css -o css/tailwind.css"
  },

Now if we run:

npm run watch

we can make updates to any of the .scss files and they will immediately transpile to .css, and with LiveReload is monitoring for changes in .css, it will show up immediately.

In this setup, Gulp looks for SCSS files inside the src/scss directory, compiles them, and outputs the resulting CSS files in the css directory, maintaining the same folder structure.

Integrating with Drupal Libraries

Once you have multiple CSS files generated for different parts of your theme, you can integrate them with Drupal’s library system. In your theme’s .libraries.yml file, you can define libraries that include these CSS files:

global-styling:
  css:
    theme:
      css/layout/header.css: {}
      css/layout/main.css: {}
      css/layout/footer.css: {}

…and so on.

In this example, global-styling is a library that includes header.css, footer.css, and main.css. You can then attach this library to your theme’s templates or preprocess functions to load these styles where needed:

/**
 * Implements hook_preprocess_HOOK() for page templates.
 */
function mytheme_preprocess_page(&$variables) {
  // Attach the 'global-styling' library.
  $variables['#attached']['library'][] = 'mytheme/global-styling';
}

(or you could attach a library directly in your template)

By adopting this approach, you maintain the modular and performant nature of Drupal theming. Your site only loads the CSS necessary for the components present on each page, which leads to faster load times and better UX.

In the next chapter, we’ll share some tips and best practices to get the most out of using SCSS and Tailwind CSS in your Drupal theme development.

6. Tips and Best Practices

Developing a Drupal theme using Tailwind CSS and SCSS can be incredibly powerful and efficient. However, it’s important to strike the right balance and ensure your theme is optimized for performance. Here are some key tips and best practices to help you make the most of your theming experience.

Balancing Tailwind and Custom SCSS

Tailwind CSS provides a utility-first approach, which is great for quickly building layouts and components. However, there are times when writing custom SCSS is more practical. Here’s how to maintain a good balance:

  • Use Tailwind for Layout and Spacing: Utilize Tailwind’s utility classes for margins, paddings, flexbox, grid, and other layout-related styles. This approach is quicker and keeps your templates clean.
  • Reserve SCSS for Theming and Complex Styles: When you need to apply more complex styles or theme-specific customizations that go beyond utility classes, use SCSS. This includes intricate animations, pseudo-elements, or when you need to leverage SCSS variables or mixins.

Another area where it can be really useful to write SCSS is when you need to override some styles coming from the base theme and you don’t want have to write or include piles custom templates for some small change, or when bits of html are being rendered directly in 3rd party modules or plugins and there is no opportunity to override a template. In these cases, rather than grind at labour-intensive overrides, it can often be much simpler to write a few lines of focused SCSS

  • Avoid Overriding Tailwind with SCSS: As much as possible, avoid using SCSS to override Tailwind styles. This can lead to higher specificity issues and make your CSS harder to maintain. Tailwind’s configuration can be customized to match your theme’s design guidelines.

Optimizing for Performance

Performance is key in web development, and your theme should be as efficient as possible:

  • Purge Unused Styles: Tailwind CSS can generate a large number of utility classes, many of which you might not use. Use Tailwind’s purge feature as part of your build process to remove unused classes from your final CSS, reducing file size.
  • Minify CSS: Leave minification to Drupal, it doesn’t need to be part of your Gulp workflow. Drupal will intelligently group and minify all the css that you need for the current page load
  • Use CSS Source Maps in Development Only: Source maps are great for debugging but increase the file size. Ensure they are only included in your development builds and not in the production-ready CSS. Use your .gitignore wisely.
  • Load CSS Efficiently: Make use of Drupal’s libraries system to load CSS only where it’s needed. This reduces unnecessary requests and speeds up page load times.

By following these tips and best practices, you’ll be able to leverage the strengths of both Tailwind CSS and SCSS in your Drupal theme development, while also ensuring that your theme is performant and maintainable.

With the combination of Tailwind for rapid prototyping and the power of SCSS for detailed styling, along with the efficiency of LiveReload for real-time feedback, you’re equipped to create an impressive and efficient Drupal theme.

May your theme development fly!