Skip to content

Tailwind

Tailwind CSS support for email templates with automatic style inlining.

The Tailwind component wraps your email tree, collects all className values from child elements, compiles them with Tailwind CSS v4's compile() API, and inlines the resulting styles on each element.

Non-inlinable styles (media queries, pseudo-classes) are injected as a <style> tag inside <Head>. If these classes are used but no <Head> component is found, an error is thrown.

Import

tsx
import { Tailwind } from "@unmail/react";
vue
<script setup>
import { Tailwind } from "@unmail/vue";
</script>

Props

PropTypeDefaultDescription
childrenReact.ReactNode / slotThe email component tree to process.
configTailwindConfigTailwind configuration (optional).
ts
type TailwindConfig = Omit<Config, "content">;

The content field is omitted because classes are auto-detected from the component tree.

Usage

Basic

tsx
import { Tailwind, Html, Head, Body, Text } from "@unmail/react";

export function Email() {
  return (
    <Tailwind>
      <Html>
        <Head />
        <Body className="bg-white">
          <Text className="text-lg font-bold text-gray-900">
            Hello world
          </Text>
        </Body>
      </Html>
    </Tailwind>
  );
}
vue
<script setup>
import { Tailwind, Html, Head, Body, Text } from "@unmail/vue";
</script>

<template>
  <Tailwind>
    <Html>
      <Head />
      <Body class="bg-white">
        <Text class="text-lg font-bold text-gray-900">
          Hello world
        </Text>
      </Body>
    </Html>
  </Tailwind>
</template>

With pixelBasedPreset

Email clients don't support rem units. The pixelBasedPreset overrides Tailwind's default rem-based spacing and font sizes with pixel equivalents.

tsx
import { Tailwind } from "@unmail/react";
import { pixelBasedPreset } from "@unmail/react/tailwind";

export function Email() {
  return (
    <Tailwind config={{ presets: [pixelBasedPreset] }}>
      <Html>
        <Head />
        <Body className="bg-white">
          <Text className="text-base p-4">Hello world</Text>
        </Body>
      </Html>
    </Tailwind>
  );
}
vue
<script setup>
import { Tailwind, Html, Head, Body, Text } from "@unmail/vue";
import { pixelBasedPreset } from "@unmail/vue/tailwind";
</script>

<template>
  <Tailwind :config="{ presets: [pixelBasedPreset] }">
    <Html>
      <Head />
      <Body class="bg-white">
        <Text class="text-base p-4">Hello world</Text>
      </Body>
    </Html>
  </Tailwind>
</template>

Sample values from the preset:

CategoryClassValue
Font sizetext-xs12px
Font sizetext-sm14px
Font sizetext-base16px
Font sizetext-lg18px
Font sizetext-xl20px
Font sizetext-2xl24px
Spacingp-1, m-14px
Spacingp-2, m-28px
Spacingp-3, m-312px
Spacingp-4, m-416px

Custom config

tsx
import { Tailwind, Html, Head, Body, Text } from "@unmail/react";
import { pixelBasedPreset } from "@unmail/react/tailwind";

export function Email() {
  return (
    <Tailwind
      config={{
        presets: [pixelBasedPreset],
        theme: {
          extend: {
            colors: {
              brand: "#007bff",
            },
          },
        },
      }}
    >
      <Html>
        <Head />
        <Body>
          <Text className="text-brand font-bold">Branded text</Text>
        </Body>
      </Html>
    </Tailwind>
  );
}
vue
<script setup>
import { Tailwind, Html, Head, Body, Text } from "@unmail/vue";
import { pixelBasedPreset } from "@unmail/vue/tailwind";

const tailwindConfig = {
  presets: [pixelBasedPreset],
  theme: {
    extend: {
      colors: {
        brand: "#007bff",
      },
    },
  },
};
</script>

<template>
  <Tailwind :config="tailwindConfig">
    <Html>
      <Head />
      <Body>
        <Text class="text-brand font-bold">Branded text</Text>
      </Body>
    </Html>
  </Tailwind>
</template>

Notes

  • <Tailwind> must wrap the entire email tree (outside <Html>).
  • A <Head> component must exist inside <Tailwind> for media query and pseudo-class support.
  • Tailwind CSS v4 is used under the hood.
  • Uses React Suspense internally, which is handled automatically by the render() function.

Released under the MIT License.