You've successfully subscribed to AEM News
Great! Next, complete checkout for full access to AEM News
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.
Success! Your billing info is updated.
Billing info update failed.

BEM & Tailwind

Joshua Zeltman
Joshua Zeltman

CSS - like many things in the world of programming - is difficult to organize, name, and maintain. This scales dramatically through the lifecycle of a project. There are many approaches to wrangle the complexity and two popular methods today are BEM and Tailwind.

BEM CSS is a naming convention while Tailwind is a library of utility classes. Neither ships with components like other CSS frameworks - e.g. Bootstrap. Tailwind aims to limit CSS growth by encouraging you write your CSS via class names on markup. BEM encourages you towards semantic class names on your markup, tied to styles living in your CSS files.

The BEM methodology has been around for 10+ years and continues to be used today with good reason. It provides guidance, sensible syntax, and can be a best-practice for connecting markup and style.

Tailwind's popularity has skyrocketed in the past few years, and with good reason. It's creators have done an excellent job with the product, documentation, and marketing. It's also solving a real pain point, and doing so in a way that fits nicely into the shifting landscape of how websites and apps are being built.

It might seem that Tailwind and BEM are conceptually antithetical, but I propose that they can coexist and work well together. Furthermore I think the combination will improve organization, increase maintainability, and simplify onboarding.

The reason that they can work very well together is due to the magic of the @apply directive - which enables us to use all of Tailwind's utility classes within our CSS files. This is combined with the Tailwind config file to control what CSS you can write, which better ensures consistent, maintainable styles are written over the lifecycle of the project.

Using Tailwind in CSS files differs from the promoted usage, but there are times when you might not control all of the markup you need to style. For example, the Adobe Experience Manager (AEM) ships with pre-made core components where you don't control the markup.

Additionally, Adobe has built the core components using BEM styling for the class names and promotes BEM as a best practice. One of the reasons why these components are built this way is that the same component can be used on multiple different websites and applications. Therefore embedding the Tailwind class names into the markup in this scenario is both not possible and not desirable.

However, this provides a great example of how we can use Tailwind together with BEM and an opportunity to utilize Tailwind's presets as well.

Let's start with how we can use Tailwind and BEM together using the Button core component.

In this example, we are not going to output the Tailwind utility classes to our CSS as we will not be adding them to our markup. As a result, our main CSS file will only include the Tailwind base styles. This also means we don't need to configure the purge CSS because Tailwind won't be outputting thousands of classes we won't use.

/* main.css */
@tailwind base;

Now the markup for the button component looks like this:

<!-- button.html -->
<div class="button">
  <button id="button-id" class="cmp-button">
    <span class="cmp-button__text">Button</span>
  </button>
</div>

Which means our raw CSS for this component looks like this:

/* components/button.css */
.cmp-button {}
.cmp-button__text {}

Normally we would just write our custom CSS in our button.css file and call it a day, however, we want to use Tailwind to ensure every developer on the project follows the design system.

With that in mind, here's an example of what our styles could look like:

/* components/button.css */
.cmp-button {
	@apply py-2 px-4 rounded border-none font-bold outline-none bg-indigo-500 text-white;

	&:focus,
	&:hover {
		@apply shadow:outline bg-indigo-700;
	}
}

If we wanted to extend this styling for use on multiple websites with different themes, we could take advantage of Tailwind's presets. Here's a quick explanatory video for Tailwind presets

// @acmecorp/tailwind-config.js
module.exports = {
  presets: [require('tailwindcss/defaultConfig')],
  theme: {
		colors: {
			primary: {
				100: '#e7f8fa',
				300: '#196ecf',
				500: '#00bed5',
				600: '#00a8f7',
				700: '#003da1',
				900: '#002677',
			},
			secondary: { /* etc */ },
			tertiary: { /* etc */ },
		},
		fontFamily: {
			brand: // @acmecorp font family
		},
	}
}

Now we can use this schema on our two sub-brands:

// brand-1/tailwind.config.js
module.exports = {
  presets: [require('@acmecorp/tailwind-config')],
  theme: {
		extend: {
			colors: {
				// override with brand colors
			}
		}
	}
}
// brand-2/tailwind.config.js
module.exports = {
  presets: [require('@acmecorp/tailwind-config')],
  theme: {
		extend: {
			colors: {
				primary: {
					// override with brand colors
				}
			},
			fontFamily: {
				// override font family here
			}
		}
	}
}

Now that we have updated the Tailwind config to use brand colors instead of the default Tailwind colors, we can update our Button styles accordingly:

/* components/button.css */
.cmp-button {
	@apply py-2 px-4 rounded border-none font-bold outline-none bg-primary-500 text-white;

	&:focus,
	&:hover {
		@apply shadow:outline bg-primary-700;
	}
}

And with that, we now have differently styled buttons for each of our two brands by leveraging Tailwind's presets while using BEM class names.

Tailwind and BEM work nicely together here, and in another example we can use the 'Modifier' syntax to add a new variant of our Button. In this example we are using the AEM Style System to apply additional class names to our wrapping element to target styling and functionality.

<!-- button.html -->
<div class="button --secondary">
  <button id="button-id" class="cmp-button">
    <span class="cmp-button__text">Button</span>
  </button>
</div>

In our CSS we are able to use this modifier like so:

/* components/button--secondary.css */
.button.\\--secondary {
	.cmp-button {
		@apply bg-secondary-500;

		&:focus,
		&:hover {
			@apply bg-secondary-700;
		}
	}
}

We have now extended the styles of our button component to use the secondary brand color using BEM's modifier syntax along with Tailwind's preset config and the @apply directive.

Wrapping Up

As we have seen Tailwind and BEM are compatible and can help maintain your project styles, improve organization, and ensure styling follows your design system. Tailwind and BEM are especially well suited for scenarios where you don't control your markup, but want power, flexibility, and consistency with your styling. Moreover, Tailwind and BEM are a fantastic solution for markup reuse on different projects as well by leveraging Tailwind's presets.

ArticlesTailwindNo-Code

Joshua Zeltman

Joshua Zeltman is an Experience Technologist, AEM Rockstar finalist, and founder of AEM.News with more than six years of experience working within the AEM ecosystem.

Comments

Sign in or become a AEM News member to join the conversation.
Just enter your email below to get a log in link.


No-Code Table of Contents

Intro to AEM

  • Intro to Core Components
  • Intro to the Style System
  • Intro to Editable Templates
  • Intro to AEM Grid System
  • Intro to Client libraries

Rapid Prototyping

Design Process

  • Introduction to Design Tokens
  • Mapping your design to Core Components
  • Using your existing design with Core Components
  • Extracting design into Design Tokens
  • Extracting design into Style System

Development Process

  • Core Components out-of-the-box
  • Intro to BEM and TailwindCSS
  • Base styling from Design Tokens
  • Implementing design for components
  • Creating Style System variations
  • Adding more functionality with JavaScript