react-email icon indicating copy to clipboard operation
react-email copied to clipboard

feat(react-email): Add Tailwind component

Open vinicoder opened this issue 2 years ago • 2 comments

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>
  );
};

vinicoder avatar Jan 26 '23 02:01 vinicoder

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>
  );
}

vinicoder avatar Jan 26 '23 03:01 vinicoder

up!

ElanUtta avatar Jan 27 '23 15:01 ElanUtta

Going to merge and release it under beta to do some more testing. Thanks @vinicoder ❤️

bukinoshita avatar Jan 27 '23 20:01 bukinoshita

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!

dreinon avatar Jan 31 '23 11:01 dreinon

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.

vinicoder avatar Jan 31 '23 12:01 vinicoder

Awesome thanks!

dreinon avatar Jan 31 '23 12:01 dreinon

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.

vinicoder avatar Feb 01 '23 11:02 vinicoder

Thanks!

dreinon avatar Feb 01 '23 11:02 dreinon

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 avatar Feb 05 '23 06:02 vojto

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.

vinicoder avatar Feb 06 '23 13:02 vinicoder

Any updates? Thanks! @vinicoder

dreinon avatar Feb 16 '23 17:02 dreinon

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. 🙃

vinicoder avatar Feb 16 '23 17:02 vinicoder