Skip to content

Styling Emails with Tailwindcss

Thanks to Lucan van Staden for sharing this approach!

Many thanks to Lucan van Staden from ProxiBlue for first documenting this approach and sharing it!


Lucas was tasked to build emails for a site, and as per Hyva docs styles for email need to be built using Less. So basically no nice Tailwind utility classes.

This will not do - he wanted to use tailwind classes, even if just to generate the css that will be used in the end.

So what he came up with was not too difficult to set up, and using @apply, a style sheet can be generated.
This means utility classes can be used as a source for email style generation.

Here is his setup (this is just a first attempt, so if you see something that can be done better, please speak up).

Step-by-Step guide

The following all happens in a theme folder (app/design/frontend/Vendor/ThemeName) after following the preparations on the Styling Emails documentation.

  1. Create a folder in your theme: web/tailwind/emails

  2. Create a custom postcss config inside the new emails/ folder, named: postcss.config.js (the postcss cli can specify a config folder, not a config file, so that is why we need the emails folder)

  3. The content of the new config file

    module.exports = {
      plugins: [
        require('tailwindcss')({ config: './emails/' }),
    This is to specify a new tailwind config file. Why? Seems Less cannot handle the rgba color syntax:

    "CSS inlining error: error evaluating function `rgba` color functions take numbers as parameters"
  4. Now create an alternative tailwind config file named, but in the emails/ folder (just to keep it all nice and contained)

  5. Place its content as follows.
    This disabled the rgba colors, and colors will be basic HEX, so Less does not choke.

    const defaultConfig = require('../tailwind.config.js');
    module.exports = {
      corePlugins: {
        backdropOpacity: false,
        backgroundOpacity: false,
        borderOpacity: false,
        divideOpacity: false,
        ringOpacity: false,
        textOpacity: false
    As you can see, this imports your main config, so you get all your tailwind setups for your theme

  6. Install postcss-cli plugin by running the command

    npm install --save-dev postcss-cli

  7. Edit the theme package.json and add a new script action:

    "build-email": "npx postcss --config ./emails theme/email.css -o ../css/source/_theme.less",
    Here Lucas chose to put his source file in the theme/ folder, and the output must override the base _theme.less file, as what you had copied over following the Hyva docs noted above.

Exclude _theme.less from git

The file web/css/source/_theme.less should be excluded from git when following this approach, since it is generated each time email-build is run.

That's it 🙂

Tailwind CSS for Emails Example

Create an example theme/email.css file containing:

.footer {
   @apply border-t-2 border-primary;
   .span {
       @apply bg-primary;

Then run: npm run build-email

% npm run build-email
   ✔ /vagrant/sites/magento-pipeline/src/app/design/frontend/Bom/custom/web/tailwind
   13:43 $ npm run build-email

> tailwind@1.0.0 build-email /vagrant/sites/magento-pipeline/src/app/design/frontend/Bom/custom/web/tailwind
> npx postcss --config ./emails theme/email.css -o ../css/source/_theme.less

The generated _theme.less file now contains

.footer {
  border-top-width: 2px;
  border-color: #DEDEDE;

.footer .span {
  background-color: #001F43;

Note: Lucas has not used this in production, but testing with yireo/magento2-emailtester2 it looks like it is working ok.

Using Tailwind to generate Less

This section might be expanded over time with tips and quirks to consider when using Tailwindcss to generate less.

Custom Background Images

When specifying a custom background image, be sure to encase the URL in single quotes, otherwise the param is not processed by less:

 background-image: url('@{baseDir}css/background/illustration_1.svg');

Border Styles

Using border styles like border-b or border-t in emails doesn't work with the above setup.
We haven't had time to investigate why, so the issue can be overcome in the meantime with a bit of mixed vanilla styles declarations:

.main-links a {
    @apply text-primary-headings border-bom_colors-charcoal-dark;
    border-bottom: 1px solid;