react-email
react-email copied to clipboard
feat(react-email): Add Tailwind component
This PR introduces a new feature that enables TailwindCSS styles and classes when creating email templates.
Motivation: The motivation behind this is that Tailwind is a widely used and efficient styling tool, and adding support for it in email templates will improve the developer experience. Should fix: https://github.com/resendlabs/react-email/issues/219 and https://github.com/resendlabs/react-email/issues/361
How it works: The feature uses a wrapper component that scans through all child elements, including nested ones, and converts any found Tailwind classes into inline CSS using a custom library.
How to use it: To use this feature, simply wrap your content in the provided wrapper component.
Example: Note that it is also possible to pass Tailwind custom options. You can do this manually or import your file from your project, whichever you prefer.
import { Button } from '@react-email/button';
import { Tailwind } from '@react-email/tailwind';
const Email = () => {
return (
<Tailwind
//Config is optional
config={{
theme: {
extend: {
colors: {
'custom-color': '#ff0000',
},
},
},
}}
>
<Button
href="https://example.com"
className="text-custom-color bg-white mx-auto"
>
Click me
</Button>
</Tailwind>
);
};
I recreated this Vercel template from the demos page so you can quickly try it out.
import { Button } from '@react-email/button';
import { Container } from '@react-email/container';
import { Head } from '@react-email/head';
import { Hr } from '@react-email/hr';
import { Html } from '@react-email/html';
import { Img } from '@react-email/img';
import { Link } from '@react-email/link';
import { Preview } from '@react-email/preview';
import { Section } from '@react-email/section';
import { Text } from '@react-email/text';
import { Tailwind } from '@react-email/tailwind';
import * as React from 'react';
export default function Email() {
const baseUrl = "https://react-email-demo-fpsq7bfkd-resend.vercel.app";
// OR: const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : '';
return (
<Html>
<Head />
<Preview>Join bukinoshita on Vercel</Preview>
<Tailwind>
<Section className="bg-white my-auto mx-auto font-sans">
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] w-[465px]">
<Section className="mt-[32px]">
<Img
src={`${baseUrl}/static/vercel-logo.png`}
width="40"
height="37"
alt="Vercel"
className="my-0 mx-auto"
/>
</Section>
<Text className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
Join <strong>My Project</strong> on <strong>Vercel</strong>
</Text>
<Text className="text-black text-[14px] leading-[24px]">
Hello zenorocha,
</Text>
<Text className="text-black text-[14px] leading-[24px]">
<strong>bukinoshita</strong> (
<Link
href="mailto:[email protected]"
className="text-blue-600 no-underline"
>
[email protected]
</Link>
) has invited you to the <strong>My Project</strong> team on{" "}
<strong>Vercel</strong>.
</Text>
<table
className="mb-[26px]"
border={0}
cellPadding="0"
cellSpacing="10"
align="center"
>
<tr>
<td className="text-center" align="left" valign="middle">
<Img
className="rounded-full"
src={`${baseUrl}/static/vercel-user.png`}
width="64"
height="64"
/>
</td>
<td className="text-center" align="left" valign="middle">
<Img
src={`${baseUrl}/static/vercel-arrow.png`}
width="12"
height="9"
alt="invited you to"
/>
</td>
<td className="text-center" align="left" valign="middle">
<Img
className="rounded-full"
src={`${baseUrl}/static/vercel-team.png`}
width="64"
height="64"
/>
</td>
</tr>
</table>
<Section className={"text-center"}>
<Button
pX={20}
pY={12}
className="bg-[#000000] rounded text-white text-[12px] font-semibold leading-[50px] no-underline text-center"
href="https://vercel.com/teams/invite/foo"
>
Join the team
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
<br />
or copy and paste this URL into your browser:{" "}
<Link
href="https://vercel.com/teams/invite/foo"
target="_blank"
className="text-blue-600 no-underline"
rel="noreferrer"
>
https://vercel.com/teams/invite/foo
</Link>
</Text>
<Hr className="border-0 border-solid border-top border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
This invitation was intended for{" "}
<span className="text-black">zenorocha</span>.This invite was sent
from <span className="text-black">204.13.186.218</span> located in{" "}
<span className="text-black">São Paulo, Brazil</span>. If you were
not expecting this invitation, you can ignore this email. If you
are concerned about your account's safety, please reply to this
email to get in touch with us.
</Text>
</Container>
</Section>
</Tailwind>
</Html>
);
}
up!
Going to merge and release it under beta to do some more testing. Thanks @vinicoder ❤️
Hi @vinicoder! Thanks for the nice feature! I'm getting the following error when using the <Tailwind> component, actually copying and pasting your vercel template implementation:
Error [ERR_REQUIRE_ESM]: require() of ES Module /home/xx/emails/.react-email/node_modules/tw-to-css/dist/module.esm.js from /home/xx/emails/.react-email/node_modules/@react-email/tailwind/dist/index.js not supported.
Instead change the require of module.esm.js in /home/xx/emails/.react-email/node_modules/@react-email/tailwind/dist/index.js to a dynamic import() which is available in all CommonJS modules.
Any ideas on how to solve it? Thanks!
Hi @vinicoder! Thanks for the nice feature! I'm getting the following error when using the component, actually copying and pasting your vercel template implementation:
Error [ERR_REQUIRE_ESM]: require() of ES Module /home/xx/emails/.react-email/node_modules/tw-to-css/dist/module.esm.js from /home/xx/emails/.react-email/node_modules/@react-email/tailwind/dist/index.js not supported. Instead change the require of module.esm.js in /home/xx/emails/.react-email/node_modules/@react-email/tailwind/dist/index.js to a dynamic import() which is available in all CommonJS modules.Any ideas on how to solve it? Thanks!
Hi @dreinon! Thanks for the feedback! This has already been fixed in the library (tw-to-css) we use under the hood to convert the Tailwind styles and will soon be updated here.
Awesome thanks!
Hi @dreinon! We pushed a fix yesterday (available on @react-email/[email protected]), and it should be working as expected now. Please, let us know how it goes.
Thanks!
Awesome work, this was super easy to set up.
In case of custom components, it seems like I need to wrap each one with Tailwind like this:
const H2: FC<{ children: ReactElement }> = ({ children }) => {
return (
<Tailwind>
<Heading as="h2" className="text-xl text-zinc-900">
{children}
</Heading>
</Tailwind>
);
};
Is this by design?
Awesome work, this was super easy to set up.
In case of custom components, it seems like I need to wrap each one with Tailwind like this:
const H2: FC<{ children: ReactElement }> = ({ children }) => { return ( <Tailwind> <Heading as="h2" className="text-xl text-zinc-900"> {children} </Heading> </Tailwind> ); };Is this by design?
@vojto - This shouldn't be necessary. The component should only be around the email body. I'll investigate to understand what happens. Thanks for reporting.
Any updates? Thanks! @vinicoder
Any updates? Thanks! @vinicoder
Hey @dreinon! I found the bug and am working to fix it. Basically, the way it passed between React nested components doesn't work correctly for this purpose, so I'm changing how we pass through nested components.
It's almost done, so I should have something to publish soon. 🙃