Tailwind CSS

Maizzle uses Tailwind CSS 4, configured and optimized for email client compatibility.

You style your templates or components with Tailwind like you're used to, and the framework compiles and lowers the modern CSS syntax so that it works across all major email clients.

Usage

Use the <Tailwind> component to wrap any part of your template:

emails/example.vue
<template>
  <Html>
    <Head />
    <Body>
      <Tailwind>        <Container class="bg-slate-100 p-4">
          <Text class="text-lg text-slate-800">Hello!</Text>
        </Container>
      </Tailwind>    </Body>
  </Html>
</template>

If you prefer a more hands-on approach, you may pull @maizzle/tailwindcss into your template yourself, either inline in a <style> tag or from an external stylesheet via a <link> tag inside your template's <Head>:

<template>
  <Html>
    <Head>
      <style>
        @import "@maizzle/tailwindcss";
      </style>
    </Head>
    <Body class="bg-slate-100">
      <Container>
        <Text class="text-lg text-slate-800">Hello!</Text>
      </Container>
    </Body>
  </Html>
</template>
<template>
  <Html>
    <Head>
      <link rel="stylesheet" href="../tailwind.css">
    </Head>
    <Body class="bg-slate-100">
      <Container>
        <Text class="text-lg text-slate-800">Hello!</Text>
      </Container>
    </Body>
  </Html>
</template>

At build time the inline-link transformer replaces the <link> with a <style> tag containing the file's contents, which then goes through normal CSS processing.

Or just use our <Layout> component, it has everything set up for you:

emails/example.vue
<template>
  <Layout>
    <Container class="bg-slate-100 p-4">
      <Text class="text-lg text-slate-800">Hello!</Text>
    </Container>
  </Layout>
</template>

Web vs. email

Email clients have limited and inconsistent CSS support. Maizzle uses several strategies to bridge the gap between the modern Tailwind CSS and email client rendering engines.

FeatureWebMaizzle
CSS variablesSupportedSupported (resolved at build time)
oklch() colorsSupportedSupported (lowered to HEX)
CSS nesting like in
:hover or @media queries
SupportedSupported (flattened)
Class names with :SupportedRewritten (sm:blocksm-block)

Modern syntax

Tailwind CSS 4 uses modern CSS features like oklch() colors, CSS nesting, and custom properties. Most email clients don't support these, but Maizzle uses Lightning CSS and a few custom tools to lower this modern syntax to simpler CSS that works everywhere.

Safe class names

Some email clients (notably Gmail) strip class names that contain special characters like : or /. The Safe Selectors transformer rewrites those cool-looking Tailwind selectors like sm:text-lg to sm-text-lg so they work everywhere.

CSS inlining

Gmail Android with an IMAP email address (aka GANGA) ignores <style> tags. Other clients, like older Outlooks, only use the first class in a class="" attribute, ignoring the rest.

Maizzle inlines Tailwind CSS utilities into style attributes, so your styling stays consistent.

<template>
  <Html>
    <Head />
    <Body>
      <Tailwind>
        <Text class="text-lg hover:text-blue-600">Hello</Text>
      </Tailwind>
    </Body>
  </Html>
</template>
<head>
  <style>
    .hover-text-blue-600:hover {
      color: #2563eb !important;
    }
  </style>
</head>
<body>
  <p 
    class="hover-text-blue-600" 
    style="margin: 16px 0; font-size: 18px; line-height: 28px;"
  >Hello</p>
</body>

Custom CSS

If you really have to, you can combine Tailwind with your own custom or inline CSS:

emails/example.vue
<template>
  <Html>
    <Head>
      <style>
        @import "@maizzle/tailwindcss";

        .custom-border {
          @apply rounded-lg;
          border: 2px solid #e2e8f0;
        }
      </style>
    </Head>
    <Body>
      <Container class="custom-border p-4" style="background-color: #facade;">
        ...
      </Container>
    </Body>
  </Html>
</template>

Raw styles

To prevent CSS inside a <style> tag from being compiled, use the raw attribute:

html
<style raw>
  /* Tailwind compilation disabled here, but may still be inlined */
</style>

Configuration

@maizzle/tailwindcss is the email-friendly Tailwind CSS 4 configuration that ships with Maizzle. Besides adjusting Tailwind's defaults, it adds prose styles for HTML content, MSO utilities for Outlook on Windows, and variants for targeting specific email clients.

Prose

@maizzle/tailwindcss ships with email-safe typography styles, similar to the @tailwindcss/typography plugin but tuned for email rendering quirks (no margin collapse, table-friendly defaults, no descendant selectors that break in Outlook).

Wrap rendered HTML or Markdown content in prose to get nicely styled headings, paragraphs, lists, blockquotes, and more out of the box:

emails/article.vue
<template>
  <Layout>
    <Container class="prose">
      <h1>Hello world</h1>
      <p>Body copy with a <a href="#">link</a>.</p>
      <ul>
        <li>Item one</li>
        <li>Item two</li>
      </ul>
    </Container>
  </Layout>
</template>

Style individual element types via prose-* variants:

html
<div class="prose prose-headings:text-brand prose-a:underline">
  <h2>Headings inherit brand color</h2>
  <a href="#">Links get an underline.</a>
</div>

Available variants:

VariantTargets
prose-headingsh1, h2, h3, h4, h5, h6
prose-h1prose-h6h1h6
prose-lead[class~="lead"]
prose-pp
prose-aa
prose-strongstrong
prose-emem
prose-kbdkbd
prose-code:not(pre) > code
prose-prepre
prose-blockquoteblockquote
prose-ulul
prose-olol
prose-lili
prose-dldl
prose-dtdt
prose-dddd
prose-tabletable
prose-theadthead
prose-trtr
prose-thth
prose-tdtd
prose-imgimg
prose-picturepicture
prose-videovideo
prose-figurefigure
prose-figcaptionfigcaption
prose-hrhr

MSO utilities

Outlook on Windows uses Microsoft Word as its rendering engine and supports a family of mso-* CSS properties for fine-tuning spacing, fonts, and layout that other clients ignore.

@maizzle/tailwindcss exposes these as utilities:

html
<!-- Font tweaks for Outlook only -->
<p class="mso-ansi-font-size-16 mso-ansi-font-weight-bold">
  Outlook only font styling
</p>

<!-- Control line-height in Outlook -->
<p class="leading-6 mso-line-height-rule-exactly mso-line-height-alt-8">
  Outlook uses a line-height of 8px instead of 24px here.
</p>

<!-- Force-hide content in Outlook -->
<div class="mso-hide-all">Hidden in Outlook</div>

A few of the most useful ones:

UtilityPurpose
mso-line-height-alt-*Set an alternate line-height that only Outlook reads
mso-line-height-rule-exactlyMake Outlook honor line-height precisely
mso-text-raise-*Vertically nudge an element
mso-hide-all / mso-hide-noneHide from / show in Outlook
mso-padding-alt-*Padding that only Outlook reads
mso-font-width-*Used by <Spacer> for horizontal spacing in Outlook

Email client targeting

@maizzle/tailwindcss ships with variants for styling elements for specific email clients.

These are useful because email clients have various levels of CSS support and rendering quirks, so you can write client-specific fixes without affecting how it looks elsewhere.

html
<p class="text-gray-950 gmail:text-gray-600">
  Dark text in most clients, but lighter in Gmail.
</p>

<p class="text-base ios:text-2xl">
  Bigger text in iOS Mail
</p>

<!-- Combine variants -->
<p class="dark:ios:text-white">
  White text in dark mode on iOS Mail
</p>

Available client variants:

VariantTargets
gmail:Gmail (web)
gmail-android:Gmail on Android
gmail-ipad:Gmail on iPad
apple-mail:Apple Mail (recent versions)
apple-mail-10:Apple Mail 10 (legacy)
ios:iOS Mail
ios-10: / ios-13:Specific iOS Mail versions
outlook-mac:Outlook for Mac
outlook-android:Outlook for Android
yahoo:Yahoo! and AOL Mail
airmail:Airmail
comcast:Comcast
edison:, edison-ios:, edison-android:Edison Mail
freenet:Freenet
notion:Notion Mail
ogsc: / ogsb:Outlook.com dark mode (text / background)
ox:Open-Xchange
spark:Spark
superhuman:Superhuman
thunderbird:Thunderbird

Escaped selectors

Yahoo and AOL will replace the .& with their wrapping ID name. To target it, the yahoo: variant compiles to a selector that contains the escape sequence \&:

css
.\& .yahoo-text-2xl { font-size: 24px !important }

That backslash is a problem for Gmail, which drops the entire <style> tag the moment its CSS parser sees a \ character. If you mix yahoo: utilities with regular Tailwind classes in the same <Tailwind> block, all of those styles end up in one <style> tag, and Gmail throws it away.

The fix is to isolate yahoo: utilities in their own <Tailwind> block so they compile into a separate <style> tag:

<template>
  <Html>
    <Head />
    <Body>
      <Tailwind>
        <Text class="yahoo:text-2xl">Limited-time offer.</Text>
      </Tailwind>

      <Tailwind>
        <Text class="text-2xl hover:text-blue-600">Limited-time offer.</Text>
      </Tailwind>
    </Body>
  </Html>
</template>
<head>
  <style>
    .\& .yahoo-text-2xl {
      font-size: 24px !important;
      line-height: 32px !important;
    }
  </style>
  <style>
    .hover-text-blue-600:hover {
      color: #2563eb !important;
    }
  </style>
</head>
<body>
  <p
    class="yahoo-text-2xl"
    style="font-size: 16px; line-height: 24px; margin-top: 16px;"
  >Limited-time offer.</p>
  <p
    class="hover-text-blue-600"
    style="margin-top: 16px; font-size: 24px; line-height: 32px;"
  >Limited-time offer.</p>
</body>

This generates two separate <style> tags, Gmail will only discard the Yahoo-targeting one.

Stacking with dark:

The ios: variant compiles to a @supports block, and Tailwind's dark: variant compiles to @media (prefers-color-scheme: dark). Stacking them, for example dark:ios:text-white, produces nested at-rules:

css
@media (prefers-color-scheme: dark) {
  @supports (-webkit-overflow-scrolling: touch) and (aspect-ratio: 1 / 1) {
    .dark-ios-text-white { color: #fff !important }
  }
}

Gmail's CSS parser does not handle nested at-rules and discards the entire <style> tag when it sees one. Use the same pattern as for Yahoo Mail: keep the stacked utilities in their own <Tailwind> block so they compile into a separate <style> tag.

emails/promo.vue
<template>
  <Html>
    <Head />
    <Body>
      <Tailwind>
        <Text class="dark:ios:text-white">Dark mode on iOS</Text>
      </Tailwind>

      <Tailwind>
        <Text class="text-base">Everything else</Text>
      </Tailwind>
    </Body>
  </Html>
</template>

Customization

Tailwind CSS 4 is configured in CSS, there's no tailwind.config.js anymore. You customize the theme directly inside the <style> tag or via the #config slot on the <Tailwind> component.

Theme tokens

Use @theme to add or override design tokens (colors, fonts, spacing, breakpoints, …):

emails/example.vue
<template>
  <Layout>
    <Head>
      <style>
        @import "@maizzle/tailwindcss";

        @theme {
          --color-brand: #4f46e5;
          --color-brand-dark: #3730a3;
          --font-display: "Inter", sans-serif;
        }
      </style>
    </Head>
    <Container class="bg-brand-dark">
      <Text class="text-brand font-display">Hello!</Text>
    </Container>
  </Layout>
</template>

Anything declared under @theme becomes a utility (text-brand, bg-brand-dark, font-display…) and is available in your templates and components.

Override defaults

Use the same token names as Tailwind's defaults to override them. For example, redefine the default sans font or the slate palette:

css
@theme {
  --font-sans: "Inter", "Helvetica Neue", Arial, sans-serif;
  --color-slate-50: #f8fafc;
  --color-slate-900: #0f172a;
}

Custom variants

Define your own variants with @custom-variant. For example, here's an any-hover: variant that only applies hover styles in clients where the user has a pointing device:

@import "@maizzle/tailwindcss";

@custom-variant any-hover {
  @media (any-hover: hover) {
    &:hover {
      @slot;
    }
  }
}
<a href="#" class="text-blue-600 any-hover:text-blue-700">
  Read more
</a>
<head>
  <style>
    @media (any-hover: hover) {
      .any-hover-text-blue-700:hover {
        color: #155dfc !important;
      }
    }
  </style>
</head>
<body>
  <a href="#" class="any-hover-text-blue-700" style="color: #155dfc;">
    Read more
  </a>
</body>

Per-template config

Pass a config slot to the <Tailwind> component to scope tokens to one template:

emails/promo.vue
<template>
  <Tailwind>
    <template #config>
      @import "@maizzle/tailwindcss";

      @theme {
        --color-brand: #f59e0b;
      }
    </template>

    <Body>
      <Text class="text-brand">Limited-time offer.</Text>
    </Body>
  </Tailwind>
</template>

Intellisense

To get Tailwind CSS Intellisense working, you need an actual .css file that imports Tailwind in your project. For example, the official Maizzle starter includes a tailwind.css file:

tailwind.css
@import "@maizzle/tailwindcss";