Using Tailwind CSS
Maizzle uses the Tailwind CSS framework, so you can quickly style HTML email templates with utility classes instead of writing inline styles.
If you've never worked with CSS utility classes in HTML emails, at first you might say:
I could just write inline CSS, it's the same thing!
However, utility classes in Tailwind are much more powerful and allow you to:
- style responsive breakpoints
- style pseudos like
:hover
- do both of the above with one class
- style for dark mode, print, reduced motion and more
- stay on-brand by using a design system in your team
... all while never having to leave your HTML.
Combine that with powerful plugins like tailwindcss-email-variants
that allow you to target email clients just by using a class like gmail:hidden
and you can quickly see why utility-first CSS with Tailwind CSS is such a powerful tool for HTML emails.
For most of the time, you won't be writing CSS anymore 😎
Workflow
The compiled Tailwind CSS is available under page.css
, so you need to make sure it is added inside a <style>
tag in your Layout's <head>
:
<!doctype html>
<html>
<head>
<style>
{{{ page.css }}}
</style>
</head>
<body>
<content />
</body>
</html>
You might have noticed that we used {{{ }}}
instead of the usual {{ }}
.
We do this to avoid double-escaping the CSS, which can break the build process when quoted property values are encountered (for example quoted font family names, background image URLs, etc.).
page.css
is added inside a
<style>
tag in the
<head>
.
Utility-first
Simply write your HTML markup and use Tailwind CSS utility classes to style elements.
Instead of writing something like this:
<table style="width: 100%;">
<tr>
<td style="padding: 24px 0; background-color: #e5e7eb;">
<h1 style="margin: 0; font-size: 36px; font-family: -apple-system, 'Segoe UI', sans-serif; color: #000000;">
Some title
</h1>
<p style="margin: 0; font-size: 16px; line-height: 24px; color: #374151;">
Content here...
</p>
</td>
</tr>
</table>
You can write:
<table class="w-full">
<tr>
<td class="py-6 px-0 bg-gray-200">
<h1 class="m-0 text-4xl font-sans text-black">
Some title
</h1>
<p class="m-0 text-base leading-6 text-gray-700">
Content here...
</p>
</td>
</tr>
</table>
Read more about the concept of utility-first CSS and familiarize yourself with the syntax in the Tailwind CSS docs. And if you're using VSCode, make sure to install the Tailwind CSS IntelliSense extension.
Components
If you're repeating the same utility classes over and over again, you can extract them to a Component so you've got a single source of truth and can make changes in one place.
Custom classes
As an alternative to creating a Component, you can extract utility classes to a custom class using Tailwind's @apply
directive.
Here's a quick example:
@layer components {
.button-danger {
@apply px-6 py-3 text-white bg-red-500;
}
}
Unlike utility classes that you add to tailwind.config.js
, you add that in a CSS file that is imported in the main tailwind.css
file.
And that brings us to...
CSS Files
The official Maizzle Starter uses a tailwind.css
file stored in src/css
.
Although optional, this file is included in order to provide an example of how you can use custom CSS in Maizzle.
For example, as we've just seen above, you might want to create a custom class based on Tailwind CSS utilities. If you want to organize custom utilities and CSS classes into files, this is how you do it.
tailwind.css
does two things:
- it imports Tailwind CSS components and utilities
- it imports custom CSS files
/* Tailwind CSS components */
@import "tailwindcss/components";
/**
* @import here any custom CSS components - that is, CSS that
* you'd want loaded before the Tailwind utilities, so the
* utilities can still override them.
*/
/* Tailwind CSS utility classes */
@import "tailwindcss/utilities";
/* Your custom utility classes */
@import "utilities";
Custom file paths
Maizzle will automatically use src/css/tailwind.css
if it's available.
If you need to use a custom file, you must define its path:
module.exports = {
build: {
tailwind: {
css: 'src/css/custom/tw-custom.css',
}
}
}
Again, this is totally optional: you can use Tailwind CSS in Maizzle without creating any CSS file at all! Doing so will only generate components and utilities based on your tailwind.config.js
.
Custom CSS
Add custom CSS files anywhere under src/css
.
Maizzle provides the utilities.css
file, which contains a sample custom utility class that Tailwind CSS doesn't provide.
@import
in
tailwind.css
must be relative to
src/css
@import
rules must come first in your
tailwind.css
file, before any CSS.
Shorthand CSS
<style>
tags. For
inline
CSS shorthand, see the
Shorthand CSS Transformer docs
.
Maizzle can rewrite your padding
, margin
, and border
CSS properties in shorthand-form, when possible.
Because utility classes map one-to-one with CSS properties, this normally doesn't have any effect with Tailwind CSS. However, in the context of <style>
tags, it's useful when you extract utilities to components, with Tailwind's @apply
.
Consider this template:
<x-main>
<div class="col">test</div>
</x-main>
Let's use @apply
to compose a col
class by extracting two padding utilities:
.col {
@apply py-2 px-1;
}
Remember to import that file:
/**
* @import here any custom CSS components - that is, classes that
* you'd want loaded before the Tailwind utilities, so the
* utilities can still override them.
*/
@import "components";
When running the build command, normally that would yield:
.col {
padding-top: 8px;
padding-bottom: 8px;
padding-left: 4px;
padding-right: 4px
}
However, Maizzle will merge those to shorthand-form, so we get this:
.col {
padding: 8px 4px;
}
This results in smaller HTML size, reducing the risk of Gmail clipping your email.
Using shorthand CSS for these properties is well supported in email clients and will make your HTML lighter, but the shorthand border (documented next) is particularly useful because it's the only way Outlook will render it properly.
padding
or
margin
, you need to specify property values for all four sides. For borders, keep reading.
Shorthand borders
To get shorthand-form CSS borders, you need to specify all these:
- border-width
- border-style
- border-color
With Tailwind's @apply
, that means you can do something like this:
.my-border {
@apply border border-solid border-blue-500;
}
... which will turn this:
<div class="my-border">Border example</div>
... into this:
<div style="border: 1px solid #3f83f8;">Border example</div>
Alternatively, you may use an arbitrary values:
<div class="[border:1px_solid_#3f83f8]">Border example</div>
Arbitrary values are actually really useful for Outlook, because something like border-b border-solid border-black
will not be shorthanded and Outlook can only apply individual borders when you write them in shorthand.
So you can do this:
<div class="[border-bottom:1px_solid_#000]">Bottom border example</div>
Arbitrary values might look like inline styles with extra steps, but it's still Tailwind so you can do stuff that you can't with inline CSS, like pseudos or media queries:
<div class="hover:[border:1px_solid_#000] sm:[border:none]">
Border example
</div>
Plugins
To use a Tailwind CSS plugin, simply npm install
it and follow its instructions to add it to plugins: []
in your tailwind.config.js
:
module.exports = {
plugins: [
require('tailwindcss-email-variants'),
],
}
See the Tailwind CSS docs for more information on plugins.
tailwindcss-email-variants
is already included in our Tailwind CSS preset, you don't need to install it separately.
Use in Template
You can use Tailwind CSS, including directives like @apply
, @responsive
, and even nested syntax, right inside a Template. You simply need to use a <stack>
to push a <style tailwindcss>
tag to the Layout being extended.
First, add a <stack name="head" />
inside your Layout's <head>
tag:
<!doctype html>
<html>
<head>
<style>
{{{ page.css }}}
</style>
<stack name="head" />
</head>
<body>
<content />
</body>
Next, push
to that stack
from a Template:
<x-main>
<push name="head">
<style tailwindcss>
a {
@apply text-blue-500;
}
@screen sm {
table {
@apply w-full;
}
}
</style>
</push>
<!-- your email HTML... -->
</x-main>
The tailwindcss
attribute is only required if you want the CSS to be compiled with Tailwind CSS. There's no need to include it if you're just writing plain CSS.
postcss attribute
You may also use the postcss
attribute instead of tailwindcss
.
This will compile the contents of <style postcss>
tags with PostCSS and any plugins you've enabled, including:
- postcss-import
- postcss-nested
- postcss-merge-longhand
However, Tailwind CSS will not be enabled in this case.
Prevent inlining
When adding a <style>
tag inside a Template, you can prevent all CSS rules inside it from being inlined by using a data-embed
attribute:
<x-main>
<push name="head">
<style tailwindcss data-embed>
img {
border: 0;
@apply leading-full align-middle;
}
</style>
</push>
<!-- your email HTML... -->
</x-main>
Although it will no longer be inlined, unused CSS will still be purged by the removeUnusedCSS Transformer.
Transforms
Maizzle doesn't include Tailwind's base reset, as that would lead to many unwanted CSS properties being inlined all over the place.
To use transform utilities, add the resets back in a <style>
tag that won't be inlined:
<x-main>
<push name="head">
<style data-embed>
*, ::before, ::after {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
}
</style>
</push>
<div class="translate-x-10 rotate-45 bg-rose-600 w-4 h-4"></div>
</x-main>
The same applies to other utilities that rely on resets through --tw-x
CSS variables, like backdrop-blur
or CSS filters.
Email client targeting
Maizzle comes with tailwindcss-email-variants, a Tailwind CSS plugin that makes it easy to style your HTML emails for certain email clients.
It adds custom variants that you may use to style elements only for certain email clients.
Gmail
Use the gmail
variant to style elements in Gmail's webmail:
<body class="body">
<div class="gmail:hidden">...</div>
</body>
The compiled HTML will include this CSS rule:
u + .body .gmail\:hidden {
display: none;
}
body
class on your
<body>
tag.
Gmail on older versions of Android requires a different selector, so there's a separate variant provided:
<body class="body">
<div class="gmail-android:hidden">...</div>
</body>
Result:
div > u + .body .gmail-android\:hidden {
display: none;
}
Apple Mail (10+)
The apple-mail
variant will target Apple Mail 10 and up:
<div class="apple-mail:hidden">...</div>
Result:
.Singleton .apple-mail\:hidden {
display: none;
}
iOS Mail (10+)
Use the ios
variant to target iOS Mail 10 and up:
<div class="ios:hidden">...</div>
Result:
@supports (-webkit-overflow-scrolling:touch) and (color:#ffff) {
.ios\:hidden {
display: none;
}
}
iOS Mail (15)
Use the ios-15
variant if you need to target iOS Mail 15 specifically:
<div class="ios-15:hidden">...</div>
Result:
@supports (-webkit-overflow-scrolling:touch) and (aspect-ratio: 1 / 1) {
.ios-15\:hidden {
display: none;
}
}
Outlook.com dark mode
Use the ogsc
and ogsb
variants to change color
and background-color
of elements in Outlook.com dark mode.
<!-- Color -->
<div class="ogsc:text-slate-200">...</div>
<!-- Background color -->
<div class="ogsb:bg-slate-900">...</div>
Result:
[data-ogsc] .ogsc\:text-slate-200 {
color: #e2e8f0;
}
[data-ogsb] .ogsb\:bg-slate-900 {
background-color: #0f172a;
}
Open-Xchange
Use the ox
variant to target webmail clients that are powered by Open-Xchange.
Some of these email clients include Comcast, Libero, 1&1 MailXchange, Network Solutions Secure Mail, Namecheap Email Hosting, Mailbox.org, 123-reg Email, acens Correo Professional, Home.pl Cloud Email Xchange, Virgin Media Mail, and Ziggo Mail.
<div class="ox:hidden">...</div>
Result:
.ox\:hidden[class^="ox-"] {
display: none;
}
Outlook CSS
Outlook and Office 365 on Windows support proprietary mso-
CSS properties.
Maizzle includes tailwindcss-mso, allowing you to use Outlook-only utilities:
<p class="mso-hide-all">...</p>
These are utility classes that work just as you'd expect - they support arbitrary values:
<p class="mso-color-alt-[#ffcc00]">...</p>
... and, where it makes sense, negative values too:
<p class="-mso-text-raise-4">...</p>