diff --git a/.changeset/pink-rabbits-deny.md b/.changeset/pink-rabbits-deny.md
new file mode 100644
index 0000000000..043905ec10
--- /dev/null
+++ b/.changeset/pink-rabbits-deny.md
@@ -0,0 +1,5 @@
+---
+"@react-email/components": major
+---
+
+tailwind: update to using tailwindcss@v4
diff --git a/.changeset/pre.json b/.changeset/pre.json
new file mode 100644
index 0000000000..47636f018f
--- /dev/null
+++ b/.changeset/pre.json
@@ -0,0 +1,39 @@
+{
+ "mode": "pre",
+ "tag": "canary",
+ "initialVersions": {
+ "demo": "0.0.0",
+ "docs": "0.0.0",
+ "web": "0.0.0",
+ "@benchmarks/preview-server": "0.0.0",
+ "@benchmarks/tailwind-component": "0.0.0",
+ "@react-email/body": "0.1.0",
+ "@react-email/button": "0.2.0",
+ "@react-email/code-block": "0.1.0",
+ "@react-email/code-inline": "0.0.5",
+ "@react-email/column": "0.0.13",
+ "@react-email/components": "0.5.7",
+ "@react-email/container": "0.0.15",
+ "create-email": "1.2.2",
+ "@react-email/font": "0.0.9",
+ "@react-email/head": "0.0.12",
+ "@react-email/heading": "0.0.15",
+ "@react-email/hr": "0.0.11",
+ "@react-email/html": "0.0.11",
+ "@react-email/img": "0.0.11",
+ "@react-email/link": "0.0.12",
+ "@react-email/markdown": "0.0.16",
+ "@react-email/preview": "0.0.13",
+ "@react-email/preview-server": "4.3.1",
+ "react-email": "4.3.1",
+ "email-dev": "0.0.2",
+ "@react-email/render": "1.4.0",
+ "@react-email/row": "0.0.12",
+ "@react-email/section": "0.0.16",
+ "@react-email/tailwind": "1.2.2",
+ "@react-email/text": "0.1.5",
+ "tsconfig": "0.0.0",
+ "playground": "0.0.9"
+ },
+ "changesets": ["pink-rabbits-deny", "rich-files-stick"]
+}
diff --git a/.changeset/rich-files-stick.md b/.changeset/rich-files-stick.md
new file mode 100644
index 0000000000..9f5ca355a2
--- /dev/null
+++ b/.changeset/rich-files-stick.md
@@ -0,0 +1,6 @@
+---
+"@react-email/tailwind": major
+---
+
+update to using tailwindcss@v4
+
diff --git a/apps/demo/emails/magic-links/aws-verify-email.tsx b/apps/demo/emails/magic-links/aws-verify-email.tsx
index ab93659440..8155680ee3 100644
--- a/apps/demo/emails/magic-links/aws-verify-email.tsx
+++ b/apps/demo/emails/magic-links/aws-verify-email.tsx
@@ -9,8 +9,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface AWSVerifyEmailProps {
verificationCode?: string;
@@ -26,59 +28,76 @@ export default function AWSVerifyEmail({
return (
- AWS Email Verification
-
-
-
-
-
-
- Verify your email address
-
- Thanks for starting the new AWS account creation process. We
- want to make sure it's really you. Please enter the following
- verification code when prompted. If you don't want to
- create an account, you can ignore this message.
-
-
- Verification code
-
- {verificationCode}
-
- (This code is valid for 10 minutes)
+
+
+ AWS Email Verification
+
+
+
+
+
+
+
+ Verify your email address
+
+
+ Thanks for starting the new AWS account creation process. We
+ want to make sure it's really you. Please enter the following
+ verification code when prompted. If you don't want to
+ create an account, you can ignore this message.
+
+
+
+ Verification code
+
+
+
+ {verificationCode}
+
+
+ (This code is valid for 10 minutes)
+
+
+
+
+
+
+ Amazon Web Services will never email you and ask you to
+ disclose or verify your password, credit card, or banking
+ account number.
-
-
-
- Amazon Web Services will never email you and ask you to disclose
- or verify your password, credit card, or banking account number.
-
-
-
-
- This message was produced and distributed by Amazon Web Services,
- Inc., 410 Terry Ave. North, Seattle, WA 98109. © 2022, Amazon Web
- Services, Inc.. All rights reserved. AWS is a registered trademark
- of{' '}
-
- Amazon.com
-
- , Inc. View our{' '}
-
- privacy policy
-
- .
-
-
-
+
+ This message was produced and distributed by Amazon Web Services,
+ Inc., 410 Terry Ave. North, Seattle, WA 98109. © 2022, Amazon Web
+ Services, Inc.. All rights reserved. AWS is a registered trademark
+ of{' '}
+
+ Amazon.com
+
+ , Inc. View our{' '}
+
+ privacy policy
+
+ .
+
+
+
+
);
}
@@ -86,90 +105,3 @@ export default function AWSVerifyEmail({
AWSVerifyEmail.PreviewProps = {
verificationCode: '596853',
} satisfies AWSVerifyEmailProps;
-
-const main = {
- backgroundColor: '#fff',
- color: '#212121',
-};
-
-const container = {
- padding: '20px',
- margin: '0 auto',
- backgroundColor: '#eee',
-};
-
-const h1 = {
- color: '#333',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '20px',
- fontWeight: 'bold',
- marginBottom: '15px',
-};
-
-const link = {
- color: '#2754C5',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '14px',
- textDecoration: 'underline',
-};
-
-const text = {
- color: '#333',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '14px',
- margin: '24px 0',
-};
-
-const imageSection = {
- backgroundColor: '#252f3d',
- display: 'flex',
- padding: '20px 0',
- alignItems: 'center',
- justifyContent: 'center',
-};
-
-const coverSection = { backgroundColor: '#fff' };
-
-const upperSection = { padding: '25px 35px' };
-
-const lowerSection = { padding: '25px 35px' };
-
-const footerText = {
- ...text,
- fontSize: '12px',
- padding: '0 20px',
-};
-
-const verifyText = {
- ...text,
- margin: 0,
- fontWeight: 'bold',
- textAlign: 'center' as const,
-};
-
-const codeText = {
- ...text,
- fontWeight: 'bold',
- fontSize: '36px',
- margin: '10px 0',
- textAlign: 'center' as const,
-};
-
-const validityText = {
- ...text,
- margin: '0px',
- textAlign: 'center' as const,
-};
-
-const verificationSection = {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
-};
-
-const mainText = { ...text, marginBottom: '14px' };
-
-const cautionText = { ...text, margin: '0px' };
diff --git a/apps/demo/emails/magic-links/linear-login-code.tsx b/apps/demo/emails/magic-links/linear-login-code.tsx
index 6c6265daf6..1e94cb076d 100644
--- a/apps/demo/emails/magic-links/linear-login-code.tsx
+++ b/apps/demo/emails/magic-links/linear-login-code.tsx
@@ -10,8 +10,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface LinearLoginCodeEmailProps {
validationCode?: string;
@@ -26,33 +28,46 @@ export const LinearLoginCodeEmail = ({
}: LinearLoginCodeEmailProps) => (
- Your login code for Linear
-
-
- Your login code for Linear
-
-
- This link and code will only be valid for the next 5 minutes. If the
- link does not work, you can use the login verification code directly:
-
- {validationCode}
-
-
- Linear
-
-
-
+
+
+ Your login code for Linear
+
+
+
+ Your login code for Linear
+
+
+
+ This link and code will only be valid for the next 5 minutes. If the
+ link does not work, you can use the login verification code
+ directly:
+
+
+ {validationCode}
+
+
+
+ Linear
+
+
+
+
);
@@ -61,74 +76,3 @@ LinearLoginCodeEmail.PreviewProps = {
} as LinearLoginCodeEmailProps;
export default LinearLoginCodeEmail;
-
-const logo = {
- borderRadius: 21,
- width: 42,
- height: 42,
-};
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const container = {
- margin: '0 auto',
- padding: '20px 0 48px',
- maxWidth: '560px',
-};
-
-const heading = {
- fontSize: '24px',
- letterSpacing: '-0.5px',
- lineHeight: '1.3',
- fontWeight: '400',
- color: '#484848',
- padding: '17px 0 0',
-};
-
-const paragraph = {
- margin: '0 0 15px',
- fontSize: '15px',
- lineHeight: '1.4',
- color: '#3c4149',
-};
-
-const buttonContainer = {
- padding: '27px 0 27px',
-};
-
-const button = {
- backgroundColor: '#5e6ad2',
- borderRadius: '3px',
- fontWeight: '600',
- color: '#fff',
- fontSize: '15px',
- textDecoration: 'none',
- textAlign: 'center' as const,
- display: 'block',
- padding: '11px 23px',
-};
-
-const reportLink = {
- fontSize: '14px',
- color: '#b4becc',
-};
-
-const hr = {
- borderColor: '#dfe1e4',
- margin: '42px 0 26px',
-};
-
-const code = {
- fontFamily: 'monospace',
- fontWeight: '700',
- padding: '1px 4px',
- backgroundColor: '#dfe1e4',
- letterSpacing: '-0.3px',
- fontSize: '21px',
- borderRadius: '4px',
- color: '#3c4149',
-};
diff --git a/apps/demo/emails/magic-links/notion-magic-link.tsx b/apps/demo/emails/magic-links/notion-magic-link.tsx
index 1fe5774d4c..ddbebb400b 100644
--- a/apps/demo/emails/magic-links/notion-magic-link.tsx
+++ b/apps/demo/emails/magic-links/notion-magic-link.tsx
@@ -7,8 +7,10 @@ import {
Img,
Link,
Preview,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface NotionMagicLinkEmailProps {
loginCode?: string;
@@ -23,66 +25,54 @@ export const NotionMagicLinkEmail = ({
}: NotionMagicLinkEmailProps) => (
- Log in with this magic link
-
- Login
-
- Click here to log in with this magic link
-
-
- Or, copy and paste this temporary login code:
-
- {loginCode}
-
- If you didn't try to login, you can safely ignore this email.
-
-
- Hint: You can set a permanent password in Settings & members → My
- account.
-
-
-
+
+
+ Log in with this magic link
+
+
+ Login
+
- Notion.so
+ Click here to log in with this magic link
- , the all-in-one-workspace
-
- for your notes, tasks, wikis, and databases.
-
-
-
+
+ Or, copy and paste this temporary login code:
+
+
+ {loginCode}
+
+
+ If you didn't try to login, you can safely ignore this email.
+
+
+ Hint: You can set a permanent password in Settings & members → My
+ account.
+
+
+
+
+ Notion.so
+
+ , the all-in-one-workspace
+
+ for your notes, tasks, wikis, and databases.
+
+
+
+
);
@@ -91,59 +81,3 @@ NotionMagicLinkEmail.PreviewProps = {
} as NotionMagicLinkEmailProps;
export default NotionMagicLinkEmail;
-
-const main = {
- backgroundColor: '#ffffff',
-};
-
-const container = {
- paddingLeft: '12px',
- paddingRight: '12px',
- margin: '0 auto',
-};
-
-const h1 = {
- color: '#333',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '24px',
- fontWeight: 'bold',
- margin: '40px 0',
- padding: '0',
-};
-
-const link = {
- color: '#2754C5',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '14px',
- textDecoration: 'underline',
-};
-
-const text = {
- color: '#333',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '14px',
- margin: '24px 0',
-};
-
-const footer = {
- color: '#898989',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
- fontSize: '12px',
- lineHeight: '22px',
- marginTop: '12px',
- marginBottom: '24px',
-};
-
-const code = {
- display: 'inline-block',
- padding: '16px 4.5%',
- width: '90.5%',
- backgroundColor: '#f4f4f4',
- borderRadius: '5px',
- border: '1px solid #eee',
- color: '#333',
-};
diff --git a/apps/demo/emails/magic-links/plaid-verify-identity.tsx b/apps/demo/emails/magic-links/plaid-verify-identity.tsx
index cd967538d3..1be7fe2b58 100644
--- a/apps/demo/emails/magic-links/plaid-verify-identity.tsx
+++ b/apps/demo/emails/magic-links/plaid-verify-identity.tsx
@@ -7,8 +7,10 @@ import {
Img,
Link,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;
@@ -23,33 +25,46 @@ export const PlaidVerifyIdentityEmail = ({
}: PlaidVerifyIdentityEmailProps) => (
-
-
- Verify Your Identity
-
- Enter the following code to finish linking Venmo.
-
-
- Not expecting this email?
-
- Contact{' '}
-
- login@plaid.com
- {' '}
- if you did not request this code.
+
+
+
+
+
+ Verify Your Identity
+
+
+ Enter the following code to finish linking Venmo.
+
+
+
+ Not expecting this email?
+
+
+ Contact{' '}
+
+ login@plaid.com
+ {' '}
+ if you did not request this code.
+
+
+
+ Securely powered by Plaid.
-
- Securely powered by Plaid.
-
+
+
);
@@ -58,99 +73,3 @@ PlaidVerifyIdentityEmail.PreviewProps = {
} as PlaidVerifyIdentityEmailProps;
export default PlaidVerifyIdentityEmail;
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily: 'HelveticaNeue,Helvetica,Arial,sans-serif',
-};
-
-const container = {
- backgroundColor: '#ffffff',
- border: '1px solid #eee',
- borderRadius: '5px',
- boxShadow: '0 5px 10px rgba(20,50,70,.2)',
- marginTop: '20px',
- maxWidth: '360px',
- margin: '0 auto',
- padding: '68px 0 130px',
-};
-
-const logo = {
- margin: '0 auto',
-};
-
-const tertiary = {
- color: '#0a85ea',
- fontSize: '11px',
- fontWeight: 700,
- fontFamily: 'HelveticaNeue,Helvetica,Arial,sans-serif',
- height: '16px',
- letterSpacing: '0',
- lineHeight: '16px',
- margin: '16px 8px 8px 8px',
- textTransform: 'uppercase' as const,
- textAlign: 'center' as const,
-};
-
-const secondary = {
- color: '#000',
- display: 'inline-block',
- fontFamily: 'HelveticaNeue-Medium,Helvetica,Arial,sans-serif',
- fontSize: '20px',
- fontWeight: 500,
- lineHeight: '24px',
- marginBottom: '0',
- marginTop: '0',
- textAlign: 'center' as const,
-};
-
-const codeContainer = {
- background: 'rgba(0,0,0,.05)',
- borderRadius: '4px',
- margin: '16px auto 14px',
- verticalAlign: 'middle',
- width: '280px',
-};
-
-const code = {
- color: '#000',
- fontFamily: 'HelveticaNeue-Bold',
- fontSize: '32px',
- fontWeight: 700,
- letterSpacing: '6px',
- lineHeight: '40px',
- paddingBottom: '8px',
- paddingTop: '8px',
- margin: '0 auto',
- display: 'block',
- textAlign: 'center' as const,
-};
-
-const paragraph = {
- color: '#444',
- fontSize: '15px',
- fontFamily: 'HelveticaNeue,Helvetica,Arial,sans-serif',
- letterSpacing: '0',
- lineHeight: '23px',
- padding: '0 40px',
- margin: '0',
- textAlign: 'center' as const,
-};
-
-const link = {
- color: '#444',
- textDecoration: 'underline',
-};
-
-const footer = {
- color: '#000',
- fontSize: '12px',
- fontWeight: 800,
- letterSpacing: '0',
- lineHeight: '23px',
- margin: '0',
- marginTop: '20px',
- fontFamily: 'HelveticaNeue,Helvetica,Arial,sans-serif',
- textAlign: 'center' as const,
- textTransform: 'uppercase' as const,
-};
diff --git a/apps/demo/emails/magic-links/raycast-magic-link.tsx b/apps/demo/emails/magic-links/raycast-magic-link.tsx
index 5a8e7c1b4e..1d091f0d1c 100644
--- a/apps/demo/emails/magic-links/raycast-magic-link.tsx
+++ b/apps/demo/emails/magic-links/raycast-magic-link.tsx
@@ -9,8 +9,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface RaycastMagicLinkEmailProps {
magicLink?: string;
@@ -25,47 +27,52 @@ export const RaycastMagicLinkEmail = ({
}: RaycastMagicLinkEmailProps) => (
- Log in with this magic link.
-
-
- 🪄 Your magic link
-
-
-
- 👉 Click here to sign in 👈
-
+
+
+ Log in with this magic link.
+
+
+
+ 🪄 Your magic link
+
+
+
+
+ 👉 Click here to sign in 👈
+
+
+
+ If you didn't request this, please ignore this email.
+
+
+
+ Best,
+ - Raycast Team
-
- If you didn't request this, please ignore this email.
+
+
+
+ Raycast Technologies Inc.
-
-
- Best,
- - Raycast Team
-
-
-
- Raycast Technologies Inc.
-
- 2093 Philadelphia Pike #3222, Claymont, DE 19703
-
-
-
+
+ 2093 Philadelphia Pike #3222, Claymont, DE 19703
+
+
+
+
);
@@ -74,47 +81,3 @@ RaycastMagicLinkEmail.PreviewProps = {
} as RaycastMagicLinkEmailProps;
export default RaycastMagicLinkEmail;
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const container = {
- margin: '0 auto',
- padding: '20px 25px 48px',
- backgroundImage: 'url("/static/raycast-bg.png")',
- backgroundPosition: 'bottom',
- backgroundRepeat: 'no-repeat, no-repeat',
-};
-
-const heading = {
- fontSize: '28px',
- fontWeight: 'bold',
- marginTop: '48px',
-};
-
-const body = {
- margin: '24px 0',
-};
-
-const paragraph = {
- fontSize: '16px',
- lineHeight: '26px',
-};
-
-const link = {
- color: '#FF6363',
-};
-
-const hr = {
- borderColor: '#dddddd',
- marginTop: '48px',
-};
-
-const footer = {
- color: '#8898aa',
- fontSize: '12px',
- marginLeft: '4px',
-};
diff --git a/apps/demo/emails/magic-links/slack-confirm.tsx b/apps/demo/emails/magic-links/slack-confirm.tsx
index a9269416ac..01c3a7f546 100644
--- a/apps/demo/emails/magic-links/slack-confirm.tsx
+++ b/apps/demo/emails/magic-links/slack-confirm.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface SlackConfirmEmailProps {
validationCode?: string;
@@ -26,121 +28,127 @@ export const SlackConfirmEmail = ({
}: SlackConfirmEmailProps) => (
- Confirm your email address
-
-
-
-
- Confirm your email address
-
- Your confirmation code is below - enter it in your open browser window
- and we'll help you get signed in.
-
+
+
+ Confirm your email address
+
+
+
+
+
+ Confirm your email address
+
+
+ Your confirmation code is below - enter it in your open browser
+ window and we'll help you get signed in.
+
-
+
-
- If you didn't request this email, there's nothing to worry about, you
- can safely ignore it.
-
+
+ If you didn't request this email, there's nothing to worry about,
+ you can safely ignore it.
+
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
- Our blog
-
- |
-
- Policies
-
- |
-
- Help center
-
- |
-
- Slack Community
-
-
- ©2022 Slack Technologies, LLC, a Salesforce company.
- 500 Howard Street, San Francisco, CA 94105, USA
-
- All rights reserved.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Our blog
+
+ |
+
+ Policies
+
+ |
+
+ Help center
+
+ |
+
+ Slack Community
+
+
+ ©2022 Slack Technologies, LLC, a Salesforce company.
+ 500 Howard Street, San Francisco, CA 94105, USA
+
+ All rights reserved.
+
+
+
+
+
);
@@ -149,77 +157,3 @@ SlackConfirmEmail.PreviewProps = {
} as SlackConfirmEmailProps;
export default SlackConfirmEmail;
-
-const footerText = {
- fontSize: '12px',
- color: '#b7b7b7',
- lineHeight: '15px',
- textAlign: 'left' as const,
- marginBottom: '50px',
-};
-
-const footerLink = {
- color: '#b7b7b7',
- textDecoration: 'underline',
-};
-
-const footerLogos = {
- marginBottom: '32px',
- paddingLeft: '8px',
- paddingRight: '8px',
-};
-
-const socialMediaIcon = {
- display: 'inline',
- marginLeft: '8px',
-};
-
-const main = {
- backgroundColor: '#ffffff',
- margin: '0 auto',
- fontFamily:
- "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
-};
-
-const container = {
- margin: '0 auto',
- padding: '0px 20px',
-};
-
-const logoContainer = {
- marginTop: '32px',
-};
-
-const h1 = {
- color: '#1d1c1d',
- fontSize: '36px',
- fontWeight: '700',
- margin: '30px 0',
- padding: '0',
- lineHeight: '42px',
-};
-
-const heroText = {
- fontSize: '20px',
- lineHeight: '28px',
- marginBottom: '30px',
-};
-
-const codeBox = {
- background: 'rgb(245, 244, 245)',
- borderRadius: '4px',
- marginBottom: '30px',
- padding: '40px 10px',
-};
-
-const confirmationCodeText = {
- fontSize: '30px',
- textAlign: 'center' as const,
- verticalAlign: 'middle',
-};
-
-const text = {
- color: '#000',
- fontSize: '14px',
- lineHeight: '24px',
-};
diff --git a/apps/demo/emails/newsletters/codepen-challengers.tsx b/apps/demo/emails/newsletters/codepen-challengers.tsx
index d72e903041..af35271c89 100644
--- a/apps/demo/emails/newsletters/codepen-challengers.tsx
+++ b/apps/demo/emails/newsletters/codepen-challengers.tsx
@@ -11,8 +11,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@@ -21,398 +23,263 @@ const baseUrl = process.env.VERCEL_URL
export const CodepenChallengersEmail = () => (
- #CodePenChallenge: Cubes
-
-
-
-
-
- View this Challenge on CodePen
-
-
-
- This week: #CodePenChallenge:{' '}
- Cubes
-
-
-
- The Shape challenge continues!
-
-
- Last week, we kicked things off with round shapes. We "rounded" up
- the Pens from week one in our{' '}
- #CodePenChallenge: Round collection.
+
+
+ #CodePenChallenge: Cubes
+
+
+
+
+
+
+ View this Challenge on CodePen
+
- This week, we move on to cubes 🧊
+
+ This week: #CodePenChallenge:{' '}
+ Cubes
+
+
+
+ The Shape challenge continues!
+
+
+ Last week, we kicked things off with round shapes. We "rounded" up
+ the Pens from week one in our{' '}
+
+ #CodePenChallenge: Round
+ {' '}
+ collection.
+
-
- Creating cubes in the browser is all about mastery of illusion. Take
- control of perspective and shadows and you can make the magic of 3D
- on a flat screen 🧙
-
+ This week, we move on to cubes 🧊
-
- This week is a fun chance to work on your CSS shape-building skills,
- or dig into a 3D JavaScript library like Three.js.
-
+
+ Creating cubes in the browser is all about mastery of illusion.
+ Take control of perspective and shadows and you can make the magic
+ of 3D on a flat screen 🧙
+
-
- This week's starter template features an ice cube emoji to help
- inspire a "cool" idea for your Pen. As always, the template is just
- as jumping off point. Feel free to incorporate the 🧊 in your
- creation, add more elements, or freeze it out completely and start
- over from scratch!
-
+
+ This week is a fun chance to work on your CSS shape-building
+ skills, or dig into a 3D JavaScript library like Three.js.
+
-
- 💪 Your Challenge: {' '}
-
- create a Pen that includes cube shapes.
-
-
+
+ This week's starter template features an ice cube emoji to help
+ inspire a "cool" idea for your Pen. As always, the template is
+ just as jumping off point. Feel free to incorporate the 🧊 in your
+ creation, add more elements, or freeze it out completely and start
+ over from scratch!
+
-
+
+ 💪 Your Challenge: {' '}
+
+ create a Pen that includes cube shapes.
+
+
-
-
- CodePen PRO combines a bunch of features that can help any
- front-end designer or developer at any experience level.
-
-
-
- Learn More
-
+
+
+
+
+ CodePen PRO combines a bunch of features that can help any
+ front-end designer or developer at any experience level.
+
+
+
+ Learn More
+
+
-
-
-
- To participate: {' '}
- Create a Pen → and tag it{' '}
-
- codepenchallenge
- {' '}
- and
-
- {' '}
- cpc-cubes
-
- . We'll be watching and gathering the Pens into a Collection, and
- sharing on Twitter and{' '}
- Instagram (Use the #CodePenChallenge tag
- on Twitter and Instagram as well).
-
-
-
-
- IDEAS!
-
-
- 🌟
-
- This week we move from 2 dimensions to three! Maybe you could
- exercise your perspective in CSS
- to create a 3D cube. Or, you can try out creating 3D shapes in
- JavaScript, using WebGL or
- building a Three.js scene.
-
-
-
-
- 🌟
-
- There's more to cubes than just six square sides. There are
- variations on the cube that could be fun to play with this
- week: cuboid shapes are
- hexahedrons with faces that aren't always squares. And if you
- want to really push the boundaries of shape, consider the 4
- dimensional tesseract!
-
-
-
-
- 🌟
-
- Here's a mind-bending idea that can combine the round shapes
- from week one with this week's cube theme:{' '}
- Spherical Cubes 😳 Solving
- longstanding mathematical mysteries is probably outside the
- scope of a CodePen challenge, but you could use front-end
- tools to explore fitting spheres into cubes, or vice-versa.
-
-
-
-
- RESOURCES!
-
-
- 📖
-
- Learn all about{' '}
- How CSS Perspective Works and
- how to build a 3D CSS cube from scratch in Amit Sheen's
- in-depth tutorial for CSS-Tricks. Or, check out stunning
- examples of WebGL cubes from Matthias Hurrle:{' '}
- Just Ice and{' '}
- Posing.
-
-
+
+ To participate: {' '}
+ Create a Pen →{' '}
+ and tag it{' '}
+
+ codepenchallenge
+ {' '}
+ and
+
+ {' '}
+ cpc-cubes
+
+ . We'll be watching and gathering the Pens into a Collection, and
+ sharing on{' '}
+ Twitter and{' '}
+ Instagram (Use
+ the #CodePenChallenge tag on Twitter and Instagram as well).
+
-
- 📖
-
- Want to go beyond the square cube? Draw inspiration from
- EntropyReversed's{' '}
- Pulsating Tesseract, Josetxu's{' '}
- Rainbow Cuboid Loader, or Ana
- Tudor's{' '}
- Pure CSS cuboid jellyfish.
+
+
+
+ IDEAS!
+
+
+ 🌟
+
+ This week we move from 2 dimensions to three! Maybe you
+ could exercise your{' '}
+
+ perspective
+ {' '}
+ in CSS to create a 3D cube. Or, you can try out creating 3D
+ shapes in JavaScript, using{' '}
+ WebGL or
+ building a{' '}
+
+ Three.js scene
+
+ .
+
+
+
+
+ 🌟
+
+ There's more to cubes than just six square sides. There are
+ variations on the cube that could be fun to play with this
+ week:{' '}
+
+ cuboid shapes
+ {' '}
+ are hexahedrons with faces that aren't always squares. And
+ if you want to really push the boundaries of shape, consider
+ the 4 dimensional{' '}
+
+ tesseract!
+
+
+
+
+
+ 🌟
+
+ Here's a mind-bending idea that can combine the round shapes
+ from week one with this week's cube theme:{' '}
+
+ Spherical Cubes
+ {' '}
+ 😳 Solving longstanding mathematical mysteries is probably
+ outside the scope of a CodePen challenge, but you could use
+ front-end tools to explore fitting spheres into cubes, or
+ vice-versa.
+
+
+
+
+
+ RESOURCES!
-
-
- 📖
-
- Did that spherical cubes concept pique your interest? Explore
- Ryan Mulligan's Cube Sphere,
- Munir Safi's{' '}
-
- 3D Sphere to Cube Animation With Virtual Trackball
- {' '}
- and Ana Tudor's{' '}
- Infinitely unpack prism for more
- mindbending cube concepts that test the boundaries of how
- shapes interact with each other.
-
-
-
-
-
+
+ 📖
+
+ Learn all about{' '}
+
+ How CSS Perspective Works
+ {' '}
+ and how to build a 3D CSS cube from scratch in Amit Sheen's
+ in-depth tutorial for CSS-Tricks. Or, check out stunning
+ examples of WebGL cubes from Matthias Hurrle:{' '}
+ Just Ice{' '}
+ and{' '}
+ Posing.
+
+
+
+
+ 📖
+
+ Want to go beyond the square cube? Draw inspiration from
+ EntropyReversed's{' '}
+
+ Pulsating Tesseract
+
+ , Josetxu's{' '}
+
+ Rainbow Cuboid Loader
+
+ , or Ana Tudor's{' '}
+
+ Pure CSS cuboid jellyfish
+
+ .
+
+
+
+
+ 📖
+
+ Did that spherical cubes concept pique your interest?
+ Explore Ryan Mulligan's{' '}
+
+ Cube Sphere
+
+ , Munir Safi's{' '}
+
+ 3D Sphere to Cube Animation With Virtual Trackball
+ {' '}
+ and Ana Tudor's{' '}
+
+ Infinitely unpack prism
+ {' '}
+ for more mindbending cube concepts that test the boundaries
+ of how shapes interact with each other.
+
+
+
+
+
-
+
+
+ Go to Challenge Page
+
+
-
-
- You can adjust your{' '}
- email preferences any time, or{' '}
- instantly opt out of emails of this
- kind. Need help with anything? Hit up{' '}
- support.
-
-
-
-
+
+
+ You can adjust your{' '}
+
+ email preferences
+ {' '}
+ any time, or{' '}
+
+ instantly opt out
+ {' '}
+ of emails of this kind. Need help with anything? Hit up{' '}
+
+ support
+
+ .
+
+
+
+
+
);
export default CodepenChallengersEmail;
-
-const main = {
- fontFamily: '"Google Sans",Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
- backgroundColor: '#505050',
- margin: '0',
-};
-
-const imgHeader = {
- margin: 'auto',
- maxWidth: '100%',
-};
-
-const imgCube = {
- maxWidth: '100%',
-};
-
-const header = {
- width: '100%',
- backgroundColor: '#191919',
- margin: '0 auto',
- paddingBottom: '30px',
- zIndex: '999',
-};
-
-const container = {
- margin: '0 auto',
- width: '648px',
- maxWidth: '100%',
- position: 'relative' as const,
-};
-
-const challengeLink = {
- backgroundColor: '#505050',
- textAlign: 'center' as const,
- padding: '10px 0',
- fontSize: '13px',
- position: 'absolute' as const,
- width: '648px',
- maxWidth: '100%',
- top: '-28px',
- margin: '0 0 16px 0',
-};
-
-const link = {
- color: '#fff',
- cursor: 'pointer',
-};
-
-const blueLink = {
- color: '#15c',
- cursor: 'pointer',
-};
-
-const heading = {
- background: '#f0d361',
- padding: '30px',
- color: '#191919',
- fontWeight: '400',
- marginBottom: '0',
-};
-
-const section = {
- margin: '0',
- background: '#fff',
- padding: '0 24px',
-};
-
-const yellowSection = {
- background: '#f5d247',
- padding: '30px',
- fontSize: '18px',
- lineHeight: '1.5',
-};
-
-const text = {
- fontSize: '16px',
-};
-
-const cubeText = { fontSize: '32px', margin: '4px 0 0 0' };
-
-const yourChallenge = {
- fontSize: '16px',
- border: '6px solid #ebd473',
- padding: '20px',
- margin: '0 0 40px 0',
-};
-
-const sectionPro = {
- marginTop: '40px',
- marginBottom: '24px',
- textAlign: 'center' as const,
- background: '#0b112a',
- color: '#fff',
- padding: '35px 20px 30px 20px',
- border: '6px solid #2138c6',
-};
-
-const imagePro = { margin: '0 auto 30px auto' };
-
-const button = {
- background: '#2138c6',
- color: '#fff',
- border: '0',
- fontSize: '15px',
- lineHeight: '18px',
- cursor: 'pointer',
- borderRadius: '4px',
- padding: '12px',
-};
-
-const resourcesTitle = {
- fontWeight: '900',
- lineHeight: '1.1',
- marginTop: '-40px',
- fontSize: '18px',
-};
-
-const ideasTitle = {
- fontWeight: '900',
- lineHeight: '1.1',
- fontSize: '18px',
-};
-
-const ideas = {
- width: '50%',
- paddingRight: '10px',
-};
-
-const resources = {
- width: '50%',
- paddingLeft: '10px',
-};
-
-const card = {
- padding: '20px',
- margin: '0 0 20px 0',
- borderRadius: '10px',
- fontSize: '36px',
- textAlign: 'center' as const,
-};
-
-const yellowCard = {
- ...card,
- background: '#fff4c8',
- border: '1px solid #f4d247',
-};
-
-const blueCard = {
- ...card,
- background: '#d9f6ff',
- border: '1px solid #92bfd0',
-};
-
-const textCard = {
- fontSize: '13px',
- textAlign: 'left' as const,
-};
-
-const goToChallenge = {
- margin: '40px 0 120px 0',
- textAlign: 'center' as const,
-};
-
-const footerButton = {
- fontSize: '26px',
- color: '#15c',
- background: '#222',
- borderRadius: '4px',
- fontWeight: 'bold',
- cursor: 'pointer',
- padding: '15px 30px',
-};
-
-const footer = {
- background: '#fff',
- color: '#505050',
- padding: '0 24px',
- marginBottom: '48px',
-};
-
-const footerText = {
- fontSize: '13px',
-};
-
-const footerLink = {
- textDecoration: 'underline',
- color: '#505050',
- cursor: 'pointer',
-};
diff --git a/apps/demo/emails/newsletters/google-play-policy-update.tsx b/apps/demo/emails/newsletters/google-play-policy-update.tsx
index 079e50de8d..0db7403682 100644
--- a/apps/demo/emails/newsletters/google-play-policy-update.tsx
+++ b/apps/demo/emails/newsletters/google-play-policy-update.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@@ -20,171 +22,167 @@ const baseUrl = process.env.VERCEL_URL
export const GooglePlayPolicyUpdateEmail = () => (
- Google Play developers
-
-
-
-
-
-
-
-
-
-
-
-
- DEVELOPER UPDATE
- Hello Google Play Developer,
-
- We strive to make Google Play a safe and trusted experience for
- users.
-
-
- We've added clarifications to our{' '}
-
- Target API Level policy
-
- . Because this is a clarification, our enforcement standards and
- practices for this policy remain the same.
-
-
-
-
- We’re noting exceptions to the{' '}
-
- Target API Level policy
-
- , which can be found in our updated{' '}
-
- Help Center article.
-
- These exceptions include permanently private apps and apps that
- target automotive or wearables form factors and are bundled within
- the same package.{' '}
-
- Learn more
-
-
-
-
-
- We’re also extending the deadline to give you more time to adjust to
- these changes. Now, apps that target API level 29 or below will
- start experiencing reduced distribution starting Jan 31, 2023 {' '}
- instead of Nov 1, 2022. If you need more time to update your app,
- you can request an extension to keep your app discoverable to all
- users until May 1, 2023.
-
-
-
-
-
- Thank you,
-
- The Google Play team
-
-
-
-
-
- Connect with us
-
-
-
-
+
+
+ Google Play developers
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+ DEVELOPER UPDATE
+
+
+ Hello Google Play Developer,
+
+
+ We strive to make Google Play a safe and trusted experience for
+ users.
+
+
+ We've added clarifications to our{' '}
+
+ Target API Level policy
-
-
-
-
+ . Because this is a clarification, our enforcement standards and
+ practices for this policy remain the same.
+
+
+
+
+ We're noting exceptions to the{' '}
+
+ Target API Level policy
+
+ , which can be found in our updated{' '}
+
+ Help Center article.
-
-
-
-
-
-
-
-
-
- © 2022 Google LLC 1600 Amphitheatre Parkway, Mountain View, CA
- 94043, USA
-
-
- You have received this mandatory email service announcement to
- update you about important changes to your Google Play Developer
- account.
-
-
-
-
+ These exceptions include permanently private apps and apps that
+ target automotive or wearables form factors and are bundled within
+ the same package.{' '}
+
+ Learn more
+
+
+
+
+
+ We're also extending the deadline to give you more time to adjust
+ to these changes. Now, apps that target API level 29 or below will
+ start experiencing reduced distribution starting{' '}
+ Jan 31, 2023 instead of Nov 1, 2022. If you need more time
+ to update your app, you can request an extension to keep your app
+ discoverable to all users until May 1, 2023.
+
+
+
+
+
+
+ Thank you,
+
+
+ The Google Play team
+
+
+
+
+
+
+ Connect with us
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ © 2022 Google LLC 1600 Amphitheatre Parkway, Mountain View, CA
+ 94043, USA
+
+
+ You have received this mandatory email service announcement to
+ update you about important changes to your Google Play Developer
+ account.
+
+
+
+
+
);
export default GooglePlayPolicyUpdateEmail;
-const main = {
- backgroundColor: '#dbddde',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
const sectionLogo = {
padding: '0 40px',
};
diff --git a/apps/demo/emails/newsletters/stack-overflow-tips.tsx b/apps/demo/emails/newsletters/stack-overflow-tips.tsx
index f7819747ea..465cd8c278 100644
--- a/apps/demo/emails/newsletters/stack-overflow-tips.tsx
+++ b/apps/demo/emails/newsletters/stack-overflow-tips.tsx
@@ -11,8 +11,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface StackOverflowTipsEmailProps {
tips?: { id: number; description: string }[];
@@ -47,107 +49,143 @@ export const StackOverflowTipsEmail = ({
}: StackOverflowTipsEmailProps) => (
- Stack overflow tips for searching
-
-
-
-
-
-
-
-
-
- Find what you want, faster
-
-
- Tips and tricks for searching on Stack Overflow
-
-
-
-
-
-
-
+
+
+ Stack overflow tips for searching
+
+
+
+
-
-
- Searching for solutions
-
-
- With more than 18 million questions, it's possible that someone has
- already provided a solution to the problem you're facing.{' '}
-
+
+
+
+
+ Find what you want, faster
+
+
+ Tips and tricks for searching on Stack Overflow
+
+
+
+
+
+
+
-
+
+
+ Searching for solutions
+
+
+ With more than 18 million questions, it's possible that someone
+ has already provided a solution to the problem you're facing.{' '}
+
+
+
+
+
+ Use the search bar at the top of the page to find what you need
+
+
+ Here are a few simple search tips to get you started:
+
+
+ {tips.map((tip) => (
+
+
+ {tip.description}
+
+
+ ))}
+
+
+
+ The more information you can put in the search bar, the more
+ likely you will be to either find the answer you need or feel
+ confident that no one else has asked the question before.
+
+
+
+
+
+ Take a break and read about the worst coder in the world
+
+
+
+
+
-
- Use the search bar at the top of the page to find what you need
-
-
- Here are a few simple search tips to get you started:
+
+
+ You're receiving this email because your Stack Overflow activity
+ triggered this tip or reminder.
-
- {tips.map((tip) => (
-
- {tip.description}
-
- ))}
-
-
- The more information you can put in the search bar, the more likely
- you will be to either find the answer you need or feel confident
- that no one else has asked the question before.
+
+ Unsubscribe from emails like this{' '}
+
+
+ Edit email settings{' '}
+
+
+ Contact us
+
+
+ Privacy
+
+
+
+
+
+
+ Stack Overflow , 110 William Street, 28th Floor, New
+ York, NY 10038
+
+
+ {'<3'}
-
-
-
-
- Take a break and read about the worst coder in the world
-
-
-
-
-
-
-
- You're receiving this email because your Stack Overflow activity
- triggered this tip or reminder.
-
-
-
- Unsubscribe from emails like this{' '}
-
-
- Edit email settings{' '}
-
-
- Contact us
-
-
- Privacy
-
-
-
-
-
-
- Stack Overflow , 110 William Street, 28th Floor, New
- York, NY 10038
-
- {'<3'}
-
-
+
+
);
@@ -156,136 +194,3 @@ StackOverflowTipsEmail.PreviewProps = {
} as StackOverflowTipsEmailProps;
export default StackOverflowTipsEmail;
-
-const main = {
- backgroundColor: '#f3f3f5',
- fontFamily: 'HelveticaNeue,Helvetica,Arial,sans-serif',
-};
-
-const headerContent = { padding: '20px 30px 15px' };
-
-const headerContentTitle = {
- color: '#fff',
- fontSize: '27px',
- fontWeight: 'bold',
- lineHeight: '27px',
-};
-
-const headerContentSubtitle = {
- color: '#fff',
- fontSize: '17px',
-};
-
-const headerImageContainer = {
- padding: '30px 10px',
-};
-
-const headerImage = {
- maxWidth: '100%',
-};
-
-const title = {
- margin: '0 0 15px',
- fontWeight: 'bold',
- fontSize: '21px',
- lineHeight: '21px',
- color: '#0c0d0e',
-};
-
-const paragraph = {
- fontSize: '15px',
- lineHeight: '21px',
- color: '#3c3f44',
-};
-
-const divider = {
- margin: '30px 0',
-};
-
-const container = {
- width: '680px',
- maxWidth: '100%',
- margin: '0 auto',
- backgroundColor: '#ffffff',
-};
-
-const footer = {
- width: '680px',
- maxWidth: '100%',
- margin: '32px auto 0 auto',
- padding: '0 30px',
-};
-
-const content = {
- padding: '30px 30px 40px 30px',
-};
-
-const logo = {
- display: 'flex',
- background: '#f3f3f5',
- padding: '20px 30px',
-};
-
-const header = {
- borderRadius: '5px 5px 0 0',
- display: 'flex',
- flexDireciont: 'column',
- backgroundColor: '#2b2d6e',
-};
-
-const buttonContainer = {
- marginTop: '24px',
- display: 'block',
-};
-
-const button = {
- backgroundColor: '#0095ff',
- border: '1px solid #0077cc',
- fontSize: '17px',
- lineHeight: '17px',
- padding: '13px 17px',
- borderRadius: '4px',
- maxWidth: '120px',
- color: '#fff',
-};
-
-const footerDivider = {
- ...divider,
- borderColor: '#d6d8db',
-};
-
-const footerText = {
- fontSize: '12px',
- lineHeight: '15px',
- color: '#9199a1',
- margin: '0',
-};
-
-const footerLink = {
- display: 'inline-block',
- color: '#9199a1',
- textDecoration: 'underline',
- fontSize: '12px',
- marginRight: '10px',
- marginBottom: '0',
- marginTop: '8px',
-};
-
-const footerAddress = {
- margin: '4px 0',
- fontSize: '12px',
- lineHeight: '15px',
- color: '#9199a1',
-};
-
-const footerHeart = {
- borderRadius: '1px',
- border: '1px solid #d6d9dc',
- padding: '4px 6px 3px 6px',
- fontSize: '11px',
- lineHeight: '11px',
- fontFamily: 'Consolas,monospace',
- color: '#e06c77',
- maxWidth: 'min-content',
- margin: '0 0 32px 0',
-};
diff --git a/apps/demo/emails/notifications/github-access-token.tsx b/apps/demo/emails/notifications/github-access-token.tsx
index ee85b69b38..adb8d18885 100644
--- a/apps/demo/emails/notifications/github-access-token.tsx
+++ b/apps/demo/emails/notifications/github-access-token.tsx
@@ -8,8 +8,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface GithubAccessTokenEmailProps {
username?: string;
@@ -24,44 +26,51 @@ export const GithubAccessTokenEmail = ({
}: GithubAccessTokenEmailProps) => (
-
- A fine-grained personal access token has been added to your account
-
-
-
-
-
- @{username} , a personal access was created on your
- account.
-
-
-
-
- Hey {username} !
-
-
- A fine-grained personal access token ( resend) was
- recently added to your account.
+
+
+
+ A fine-grained personal access token has been added to your account
+
+
+
+
+
+ @{username} , a personal access was created on your
+ account.
- View your token
-
-
- Your security audit log ・{' '}
- Contact support
-
+
+
+ Hey {username} !
+
+
+ A fine-grained personal access token ( resend) was
+ recently added to your account.
+
+
+
+ View your token
+
+
+
+
+ Your security audit log
+ {' '}
+ ・{' '}
+ Contact support
+
-
- GitHub, Inc. ・88 Colin P Kelly Jr Street ・San Francisco, CA 94107
-
-
-
+
+ GitHub, Inc. ・88 Colin P Kelly Jr Street ・San Francisco, CA 94107
+
+
+
+
);
@@ -70,58 +79,3 @@ GithubAccessTokenEmail.PreviewProps = {
} as GithubAccessTokenEmailProps;
export default GithubAccessTokenEmail;
-
-const main = {
- backgroundColor: '#ffffff',
- color: '#24292e',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
-};
-
-const container = {
- maxWidth: '480px',
- margin: '0 auto',
- padding: '20px 0 48px',
-};
-
-const title = {
- fontSize: '24px',
- lineHeight: 1.25,
-};
-
-const section = {
- padding: '24px',
- border: 'solid 1px #dedede',
- borderRadius: '5px',
- textAlign: 'center' as const,
-};
-
-const text = {
- margin: '0 0 10px 0',
- textAlign: 'left' as const,
-};
-
-const button = {
- fontSize: '14px',
- backgroundColor: '#28a745',
- color: '#fff',
- lineHeight: 1.5,
- borderRadius: '0.5em',
- padding: '12px 24px',
-};
-
-const links = {
- textAlign: 'center' as const,
-};
-
-const link = {
- color: '#0366d6',
- fontSize: '12px',
-};
-
-const footer = {
- color: '#6a737d',
- fontSize: '12px',
- textAlign: 'center' as const,
- marginTop: '60px',
-};
diff --git a/apps/demo/emails/notifications/yelp-recent-login.tsx b/apps/demo/emails/notifications/yelp-recent-login.tsx
index 5b56a4a219..dc350ae55a 100644
--- a/apps/demo/emails/notifications/yelp-recent-login.tsx
+++ b/apps/demo/emails/notifications/yelp-recent-login.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface YelpRecentLoginEmailProps {
userFirstName?: string;
@@ -39,106 +41,88 @@ export const YelpRecentLoginEmail = ({
return (
-
- Yelp recent login
-
-
-
-
-
-
-
+
+
+
+ Yelp recent login
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hi {userFirstName},
+
+
+ We noticed a recent login to your Yelp account.
+
+
+
+ Time:
+ {formattedDate}
+
+
+ Device:
+ {loginDevice}
+
+
+ Location:
+ {loginLocation}
+
+
+ *Approximate geographic location based on IP address:
+ {loginIp}
+
+
+
+ If this was you, there's nothing else you need to do.
+
+
+ If this wasn't you or if you have additional questions,
+ please see our support page.
+
+
+
+
+
+
+ Learn More
+
+
+
+
+
+
-
-
-
-
-
- Hi {userFirstName},
-
-
- We noticed a recent login to your Yelp account.
-
-
-
- Time:
- {formattedDate}
-
-
- Device:
- {loginDevice}
-
-
- Location:
- {loginLocation}
-
-
- *Approximate geographic location based on IP address:
- {loginIp}
-
-
-
- If this was you, there's nothing else you need to do.
-
-
- If this wasn't you or if you have additional questions, please
- see our support page.
-
-
-
-
-
- Learn More
-
-
-
-
-
-
-
-
-
- © 2022 | Yelp Inc., 350 Mission Street, San Francisco, CA 94105,
- U.S.A. | www.yelp.com
-
-
-
+
+
+
+ © 2022 | Yelp Inc., 350 Mission Street, San Francisco, CA 94105,
+ U.S.A. | www.yelp.com
+
+
+
+
);
};
@@ -152,51 +136,3 @@ YelpRecentLoginEmail.PreviewProps = {
} as YelpRecentLoginEmailProps;
export default YelpRecentLoginEmail;
-
-const main = {
- backgroundColor: '#fff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const paragraph = {
- fontSize: 16,
-};
-
-const logo = {
- padding: '30px 20px',
-};
-
-const buttonContainer = {
- textAlign: 'center' as const,
-};
-
-const button = {
- backgroundColor: '#e00707',
- borderRadius: 3,
- color: '#FFF',
- fontWeight: 'bold',
- border: '1px solid rgb(0,0,0, 0.1)',
- cursor: 'pointer',
- display: 'inline-block',
- padding: '12px 30px',
- textDecoration: 'none',
-};
-
-const content = {
- border: '1px solid rgb(0,0,0, 0.1)',
- borderRadius: '3px',
- overflow: 'hidden',
-};
-
-const image = {
- maxWidth: '100%',
-};
-
-const boxInfos = {
- padding: '20px',
-};
-
-const containerImageFooter = {
- padding: '45px 0 0 0',
-};
diff --git a/apps/demo/emails/receipts/apple-receipt.tsx b/apps/demo/emails/receipts/apple-receipt.tsx
index 194a2c1348..0fe12f8992 100644
--- a/apps/demo/emails/receipts/apple-receipt.tsx
+++ b/apps/demo/emails/receipts/apple-receipt.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@@ -20,473 +22,313 @@ const baseUrl = process.env.VERCEL_URL
export const AppleReceiptEmail = () => (
- Apple Receipt
-
-
-
-
-
-
-
-
- Receipt
-
-
-
-
-
- Save 3% on all your Apple purchases with Apple Card.
- 1 {' '}
-
- Apply and use in minutes
+
+
+ Apple Receipt
+
+
+
+
+
+
+
+
+
+ Receipt
+
+
+
+
+
+
+ Save 3% on all your Apple purchases with Apple Card.
+ 1 {' '}
+
+ Apply and use in minutes
+
+ 2
+
+
+
+
+
+
+
+
+
+ APPLE ID
+
+
+ alan.turing@gmail.com
+
+
+
+
+
+
+
+ INVOICE DATE
+
+
+ 18 Jan 2023
+
+
+
+
+
+
+
+ ORDER ID
+
+
+ ML4F5L8522
+
+
+
+
+ DOCUMENT NO.
+
+
+ 186623754793
+
+
+
+
+
+
+
+ BILLED TO
+
+
+ Visa .... 7461 (Apple Pay)
+
+
+ Alan Turing
+
+
+ 2125 Chestnut St
+
+
+ San Francisco, CA 94123
+
+ USA
+
+
+
+
+
+
+
+
+
+
+
+ HBO Max: Stream TV & Movies
+
+
+ HBO Max Ad-Free (Monthly)
+
+
+ Renews Aug 20, 2023
+
+
+ Write a Review
+
+
+ |
+
+
+ Report a Problem
+
+
+
+
+
+ $14.99
+
+
+
+
+
+
+
+
+
+ TOTAL
+
+
+
+
+
+ $14.99
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save 3% on all your Apple purchases.
+
+
+
+
+
+
+
+
+
+
+ Apply and use in minutes
+
+
+
+
+
+
+
+ 1. 3% savings is earned as Daily Cash and is transferred to your
+ Apple Cash card when transactions post to your Apple Card account.
+ If you do not have an Apple Cash card, Daily Cash can be applied by
+ you as a credit on your statement balance. 3% is the total amount of
+ Daily Cash earned for these purchases. See the Apple Card Customer
+ Agreement for more details on Daily Cash and qualifying
+ transactions.
+
+
+ 2. Subject to credit approval.
+
+
+ To access and use all the features of Apple Card, you must add Apple
+ Card to Wallet on an iPhone or iPad with iOS or iPadOS 13.2 or
+ later. Update to the latest version of iOS or iPadOS by going to
+ Settings > General > Software Update. Tap Download and
+ Install.
+
+
+ Available for qualifying applicants in the United States.
+
+
+ Apple Card is issued by Goldman Sachs Bank USA, Salt Lake City
+ Branch.
+
+
+ If you reside in the US territories, please call Goldman Sachs at
+ 877-255-5923 with questions about Apple Card.
+
+
+ Privacy: We use a
+
+ {' '}
+ Subscriber ID{' '}
- 2
+ to provide reports to developers.
+
+
+ Get help with subscriptions and purchases.
+
+ Visit Apple Support.
+
+
+
+ Learn how to{' '}
+
+ manage your password preferences
+ {' '}
+ for iTunes, Apple Books, and App Store purchases.
-
-
-
-
-
-
-
- APPLE ID
-
- alan.turing@gmail.com
-
-
-
-
-
-
- INVOICE DATE
- 18 Jan 2023
-
-
-
-
-
- ORDER ID
-
- ML4F5L8522
-
-
-
- DOCUMENT NO.
- 186623754793
-
-
-
-
-
- BILLED TO
-
- Visa .... 7461 (Apple Pay)
-
- Alan Turing
- 2125 Chestnut St
- San Francisco, CA 94123
- USA
-
-
-
-
-
-
-
-
-
-
- HBO Max: Stream TV & Movies
- HBO Max Ad-Free (Monthly)
- Renews Aug 20, 2023
-
- Write a Review
-
- |
-
- Report a Problem
-
-
-
- $14.99
-
-
-
-
-
-
-
- TOTAL
-
-
-
- $14.99
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save 3% on all your Apple purchases.
-
-
-
-
-
-
-
+
+ {' '}
+ You have the option to stop receiving email receipts for your
+ subscription renewals. If you have opted out, you can still view
+ your receipts in your account under Purchase History. To manage
+ receipts or to opt in again, go to{' '}
+ Account Settings.
+
+
+
+
- Apply and use in minutes
-
-
-
-
-
-
- 1. 3% savings is earned as Daily Cash and is transferred to your Apple
- Cash card when transactions post to your Apple Card account. If you do
- not have an Apple Cash card, Daily Cash can be applied by you as a
- credit on your statement balance. 3% is the total amount of Daily Cash
- earned for these purchases. See the Apple Card Customer Agreement for
- more details on Daily Cash and qualifying transactions.
-
- 2. Subject to credit approval.
-
- To access and use all the features of Apple Card, you must add Apple
- Card to Wallet on an iPhone or iPad with iOS or iPadOS 13.2 or later.
- Update to the latest version of iOS or iPadOS by going to Settings
- > General > Software Update. Tap Download and Install.
-
-
- Available for qualifying applicants in the United States.
-
-
- Apple Card is issued by Goldman Sachs Bank USA, Salt Lake City Branch.
-
-
- If you reside in the US territories, please call Goldman Sachs at
- 877-255-5923 with questions about Apple Card.
-
-
- Privacy: We use a
-
- {' '}
- Subscriber ID{' '}
-
- to provide reports to developers.
-
-
- Get help with subscriptions and purchases.
-
- Visit Apple Support.
-
-
-
- Learn how to{' '}
-
- manage your password preferences
- {' '}
- for iTunes, Apple Books, and App Store purchases.
-
-
-
- {' '}
- You have the option to stop receiving email receipts for your
- subscription renewals. If you have opted out, you can still view your
- receipts in your account under Purchase History. To manage receipts or
- to opt in again, go to{' '}
- Account Settings.
-
-
-
-
-
-
-
-
-
- Account Settings •{' '}
- Terms of Sale •{' '}
-
- Privacy Policy{' '}
-
-
-
- Copyright © 2023 Apple Inc. {' '}
- All rights reserved
-
-
-
+
+
+
+
+ Account Settings •{' '}
+ Terms of Sale •{' '}
+
+ Privacy Policy{' '}
+
+
+
+ Copyright © 2023 Apple Inc. {' '}
+ All rights reserved
+
+
+
+
);
export default AppleReceiptEmail;
-
-const main = {
- fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
- backgroundColor: '#ffffff',
-};
-
-const resetText = {
- margin: '0',
- padding: '0',
- lineHeight: 1.4,
-};
-
-const container = {
- margin: '0 auto',
- padding: '20px 0 48px',
- width: '660px',
- maxWidth: '100%',
-};
-
-const tableCell = { display: 'table-cell' };
-
-const heading = {
- fontSize: '32px',
- fontWeight: '300',
- color: '#888888',
-};
-
-const cupomText = {
- textAlign: 'center' as const,
- margin: '36px 0 40px 0',
- fontSize: '14px',
- fontWeight: '500',
- color: '#111111',
-};
-
-const supStyle = {
- fontWeight: '300',
-};
-
-const informationTable = {
- borderCollapse: 'collapse' as const,
- borderSpacing: '0px',
- color: 'rgb(51,51,51)',
- backgroundColor: 'rgb(250,250,250)',
- borderRadius: '3px',
- fontSize: '12px',
-};
-
-const informationTableRow = {
- minHeight: '46px',
-};
-
-const informationTableColumn = {
- paddingLeft: '20px',
- borderStyle: 'solid',
- borderColor: 'white',
- borderWidth: '0px 1px 1px 0px',
- minHeight: '44px',
-};
-
-const informationTableLabel = {
- ...resetText,
- color: 'rgb(102,102,102)',
- fontSize: '10px',
-};
-
-const informationTableValue = {
- fontSize: '12px',
- margin: '0',
- padding: '0',
- lineHeight: 1.4,
-};
-
-const productTitleTable = {
- ...informationTable,
- margin: '30px 0 15px 0',
- minHeight: '24px',
-};
-
-const productsTitle = {
- background: '#fafafa',
- paddingLeft: '10px',
- fontSize: '14px',
- fontWeight: '500',
- margin: '0',
-};
-
-const productIcon = {
- margin: '0 0 0 20px',
- borderRadius: '14px',
- border: '1px solid rgb(242,242,242)',
-};
-
-const productTitle = { fontSize: '12px', fontWeight: '600', ...resetText };
-
-const productDescription = {
- fontSize: '12px',
- color: 'rgb(102,102,102)',
- ...resetText,
-};
-
-const productLink = {
- fontSize: '12px',
- color: 'rgb(0,112,201)',
- textDecoration: 'none',
-};
-
-const divisor = {
- marginLeft: '4px',
- marginRight: '4px',
- color: 'rgb(51,51,51)',
- fontWeight: 200,
-};
-
-const productPriceTotal = {
- margin: '0',
- color: 'rgb(102,102,102)',
- fontSize: '10px',
- fontWeight: '600',
- padding: '0px 30px 0px 0px',
- textAlign: 'right' as const,
-};
-
-const productPrice = {
- fontSize: '12px',
- fontWeight: '600',
- margin: '0',
-};
-
-const productPriceLarge = {
- margin: '0px 20px 0px 0px',
- fontSize: '16px',
- fontWeight: '600',
- whiteSpace: 'nowrap' as const,
- textAlign: 'right' as const,
-};
-
-const productPriceWrapper = {
- display: 'table-cell',
- padding: '0px 20px 0px 0px',
- width: '100px',
- verticalAlign: 'top',
-};
-
-const productPriceLine = { margin: '30px 0 0 0' };
-
-const productPriceVerticalLine = {
- minHeight: '48px',
- paddingTop: '48px',
- borderLeft: '1px solid',
- borderColor: 'rgb(238,238,238)',
-};
-
-const productPriceLargeWrapper = { display: 'table-cell', width: '90px' };
-
-const productPriceLineBottom = { margin: '0 0 75px 0' };
-
-const block = { display: 'block' };
-
-const ctaTitle = {
- display: 'block',
- margin: '15px 0 0 0',
-};
-
-const ctaText = { fontSize: '24px', fontWeight: '500' };
-
-const walletWrapper = { display: 'table-cell', margin: '10px 0 0 0' };
-
-const walletLink = { color: 'rgb(0,126,255)', textDecoration: 'none' };
-
-const walletImage = {
- display: 'inherit',
- paddingRight: '8px',
- verticalAlign: 'middle',
-};
-
-const walletBottomLine = { margin: '65px 0 20px 0' };
-
-const footerText = {
- fontSize: '12px',
- color: 'rgb(102,102,102)',
- margin: '0',
- lineHeight: 'auto',
- marginBottom: '16px',
-};
-
-const footerTextCenter = {
- fontSize: '12px',
- color: 'rgb(102,102,102)',
- margin: '20px 0',
- lineHeight: 'auto',
- textAlign: 'center' as const,
-};
-
-const footerLink = { color: 'rgb(0,115,255)' };
-
-const footerIcon = { display: 'block', margin: '40px 0 0 0' };
-
-const footerLinksWrapper = {
- margin: '8px 0 0 0',
- textAlign: 'center' as const,
- fontSize: '12px',
- color: 'rgb(102,102,102)',
-};
-
-const footerCopyright = {
- margin: '25px 0 0 0',
- textAlign: 'center' as const,
- fontSize: '12px',
- color: 'rgb(102,102,102)',
-};
-
-const walletLinkText = {
- fontSize: '14px',
- fontWeight: '400',
- textDecoration: 'none',
-};
diff --git a/apps/demo/emails/receipts/nike-receipt.tsx b/apps/demo/emails/receipts/nike-receipt.tsx
index db13e06a87..d58e6a59a1 100644
--- a/apps/demo/emails/receipts/nike-receipt.tsx
+++ b/apps/demo/emails/receipts/nike-receipt.tsx
@@ -11,9 +11,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
-import type * as React from 'react';
+import tailwindConfig from '../tailwind.config';
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@@ -22,442 +23,336 @@ const baseUrl = process.env.VERCEL_URL
export const NikeReceiptEmail = () => (
-
- Get your order summary, estimated delivery date and more
-
-
-
-
-
- Tracking Number
- 1ZV218970300071628
-
-
- Track Package
-
-
-
-
-
-
- It's On Its Way.
-
- You order's is on its way. Use the link above to track its progress.
-
-
- We´ve also charged your payment method for the cost of your order
- and will be removing any authorization holds. For payment details,
- please visit your Orders page on Nike.com or in the Nike app.
-
-
-
-
- Shipping to: Alan Turing
-
- 2125 Chestnut St, San Francisco, CA 94123
-
-
-
-
-
-
-
-
-
-
- Brazil 2022/23 Stadium Away Women's Nike Dri-FIT Soccer Jersey
-
- Size L (12–14)
-
-
-
-
-
-
-
- Order Number
- C0106373851
-
-
- Order Date
- Sep 22, 2022
-
-
-
-
- Order Status
-
-
-
-
-
-
- Top Picks For You
-
-
-
-
-
- USWNT 2022/23 Stadium Home
-
-
- Women's Nike Dri-FIT Soccer Jersey
+
+
+
+ Get your order summary, estimated delivery date and more
+
+
+
+
+
+
+ Tracking Number
+
+
+ 1ZV218970300071628
+
+
+
+
+ Track Package
+
+
+
+
+
+
+
+
+ It's On Its Way.
+
+
+ You order's is on its way. Use the link above to track its
+ progress.
+
+
+ We´ve also charged your payment method for the cost of your order
+ and will be removing any authorization holds. For payment details,
+ please visit your Orders page on Nike.com or in the Nike app.
+
+
+
+
+
+ Shipping to: Alan Turing
+
+
+ 2125 Chestnut St, San Francisco, CA 94123
+
+
+
+
+
+
+
+
+
+
+ Brazil 2022/23 Stadium Away Women's Nike Dri-FIT Soccer Jersey
+
+
+ Size L (12–14)
+
+
+
+
+
+
+
+
+
+ Order Number
+
+
+ C0106373851
+
+
+
+
+ Order Date
+
+
+ Sep 22, 2022
+
+
+
+
+
+
+ Order Status
+
+
+
+
+
+
+
+
+ Top Picks For You
-
-
-
-
- Brazil 2022/23 Stadium Goalkeeper
+
+
+
+
+
+ USWNT 2022/23 Stadium Home
+
+
+ Women's Nike Dri-FIT Soccer Jersey
+
+
+
+
+
+ Brazil 2022/23 Stadium Goalkeeper
+
+
+ Men's Nike Dri-FIT Short-Sleeve Football Shirt
+
+
+
+
+
+ FFF
+
+
+ Women's Soccer Jacket
+
+
+
+
+
+ FFF
+
+
+ Women's Nike Pre-Match Football Top
+
+
+
+
+
+
+
+ Get Help
+
+
+
+
+ Shipping Status
+
+
+
+
+ Shipping & Delivery
+
+
+
+
+ Returns & Exchanges
+
+
+
+
+
+
+ How to Return
+
+
+
+
+ Contact Options
+
+
+
+
+
+
+
+
+
+
+
+
+ 1-800-806-6453
+
+
+
+
+
+
+ 4 am - 11 pm PT
+
+
+
+
+
+
+
+
+ Nike.com
-
- Men's Nike Dri-FIT Short-Sleeve Football Shirt
+
+
+
+
+ Men
+
+
+
+
+ Women
+
+
+
+
+ Kids
+
+
+
+
+ Customize
+
+
+
+
+
+
+
+
+
+ Web Version
+
+
+
+
+ Privacy Policy
+
+
+
+
+
+ Please contact us if you have any questions. (If you reply to
+ this email, we won't be able to see it.)
-
-
-
- FFF
- Women's Soccer Jacket
-
-
-
- FFF
-
- Women's Nike Pre-Match Football Top
+
+
+
+ © 2022 Nike, Inc. All Rights Reserved.
-
-
-
-
-
-
- Get Help
-
-
-
-
- Shipping Status
-
-
-
-
- Shipping & Delivery
-
-
-
-
- Returns & Exchanges
-
-
-
-
-
-
- How to Return
-
-
-
-
- Contact Options
-
-
-
-
-
-
-
-
-
-
-
-
- 1-800-806-6453
-
-
-
-
-
-
- 4 am - 11 pm PT
+
+
+
+ NIKE, INC. One Bowerman Drive, Beaverton, Oregon 97005, USA.
-
-
-
-
-
-
- Nike.com
-
-
-
-
- Men
-
-
-
-
- Women
-
-
-
-
- Kids
-
-
-
-
- Customize
-
-
-
-
-
-
-
-
- Web Version
-
-
- Privacy Policy
-
-
-
-
- Please contact us if you have any questions. (If you reply to this
- email, we won't be able to see it.)
-
-
-
-
- © 2022 Nike, Inc. All Rights Reserved.
-
-
-
-
- NIKE, INC. One Bowerman Drive, Beaverton, Oregon 97005, USA.
-
-
-
-
-
+
+
+
+
+
);
export default NikeReceiptEmail;
-
-const paddingX = {
- paddingLeft: '40px',
- paddingRight: '40px',
-};
-
-const paddingY = {
- paddingTop: '22px',
- paddingBottom: '22px',
-};
-
-const paragraph = {
- margin: '0',
- lineHeight: '2',
-};
-
-const global = {
- paddingX,
- paddingY,
- defaultPadding: {
- ...paddingX,
- ...paddingY,
- },
- paragraphWithBold: { ...paragraph, fontWeight: 'bold' },
- heading: {
- fontSize: '32px',
- lineHeight: '1.3',
- fontWeight: '700',
- textAlign: 'center',
- letterSpacing: '-1px',
- } as React.CSSProperties,
- text: {
- ...paragraph,
- color: '#747474',
- fontWeight: '500',
- },
- button: {
- border: '1px solid #929292',
- fontSize: '16px',
- textDecoration: 'none',
- padding: '10px 0px',
- width: '220px',
- display: 'block',
- textAlign: 'center',
- fontWeight: 500,
- color: '#000',
- } as React.CSSProperties,
- hr: {
- borderColor: '#E5E5E5',
- margin: '0',
- },
-};
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const container = {
- margin: '10px auto',
- width: '600px',
- maxWidth: '100%',
- border: '1px solid #E5E5E5',
-};
-
-const track = {
- container: {
- padding: '22px 40px',
- backgroundColor: '#F7F7F7',
- },
- number: {
- margin: '12px 0 0 0',
- fontWeight: 500,
- lineHeight: '1.4',
- color: '#6F6F6F',
- },
-};
-
-const message = {
- padding: '40px 74px',
- textAlign: 'center',
-} as React.CSSProperties;
-
-const adressTitle = {
- ...paragraph,
- fontSize: '15px',
- fontWeight: 'bold',
-};
-
-const recomendationsText = {
- margin: '0',
- fontSize: '15px',
- lineHeight: '1',
- paddingLeft: '10px',
- paddingRight: '10px',
-};
-
-const recomendations = {
- container: {
- padding: '20px 0',
- },
- product: {
- verticalAlign: 'top',
- textAlign: 'left' as const,
- paddingLeft: '2px',
- paddingRight: '2px',
- },
- title: { ...recomendationsText, paddingTop: '12px', fontWeight: '500' },
- text: {
- ...recomendationsText,
- paddingTop: '4px',
- color: '#747474',
- },
-};
-
-const menu = {
- container: {
- paddingLeft: '20px',
- paddingRight: '20px',
- paddingTop: '20px',
- backgroundColor: '#F7F7F7',
- },
- content: {
- ...paddingY,
- paddingLeft: '20px',
- paddingRight: '20px',
- },
- title: {
- paddingLeft: '20px',
- paddingRight: '20px',
- fontWeight: 'bold',
- },
- text: {
- fontSize: '13.5px',
- marginTop: 0,
- fontWeight: 500,
- color: '#000',
- },
- tel: {
- paddingLeft: '20px',
- paddingRight: '20px',
- paddingTop: '32px',
- paddingBottom: '22px',
- },
-};
-
-const categories = {
- container: {
- width: '370px',
- margin: 'auto',
- paddingTop: '12px',
- },
- text: {
- fontWeight: '500',
- color: '#000',
- },
-};
-
-const footer = {
- policy: {
- width: '166px',
- margin: 'auto',
- },
- text: {
- margin: '0',
- color: '#AFAFAF',
- fontSize: '13px',
- textAlign: 'center',
- } as React.CSSProperties,
-};
diff --git a/apps/demo/emails/reset-password/dropbox-reset-password.tsx b/apps/demo/emails/reset-password/dropbox-reset-password.tsx
index 91a4ef6d83..907fe6e021 100644
--- a/apps/demo/emails/reset-password/dropbox-reset-password.tsx
+++ b/apps/demo/emails/reset-password/dropbox-reset-password.tsx
@@ -8,8 +8,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface DropboxResetPasswordEmailProps {
userFirstname?: string;
@@ -27,39 +29,48 @@ export const DropboxResetPasswordEmail = ({
return (
- Dropbox reset your password
-
-
-
- Hi {userFirstname},
-
- Someone recently requested a password change for your Dropbox
- account. If this was you, you can set a new password here:
-
-
- Reset password
-
-
- If you don't want to change your password or didn't
- request this, just ignore and delete this message.
-
-
- To keep your account secure, please don't forward this email
- to anyone. See our Help Center for{' '}
-
- more security tips.
-
-
- Happy Dropboxing!
-
-
-
+
+
+ Dropbox reset your password
+
+
+
+
+ Hi {userFirstname},
+
+
+ Someone recently requested a password change for your Dropbox
+ account. If this was you, you can set a new password here:
+
+
+ Reset password
+
+
+ If you don't want to change your password or didn't
+ request this, just ignore and delete this message.
+
+
+ To keep your account secure, please don't forward this
+ email to anyone. See our Help Center for{' '}
+
+ more security tips.
+
+
+
+ Happy Dropboxing!
+
+
+
+
+
);
};
@@ -69,41 +80,6 @@ DropboxResetPasswordEmail.PreviewProps = {
resetPasswordLink: 'https://www.dropbox.com',
} as DropboxResetPasswordEmailProps;
-export default DropboxResetPasswordEmail;
-
-const main = {
- backgroundColor: '#f6f9fc',
- padding: '10px 0',
-};
+DropboxResetPasswordEmail.tailwindConfig = tailwindConfig;
-const container = {
- backgroundColor: '#ffffff',
- border: '1px solid #f0f0f0',
- padding: '45px',
-};
-
-const text = {
- fontSize: '16px',
- fontFamily:
- "'Open Sans', 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif",
- fontWeight: '300',
- color: '#404040',
- lineHeight: '26px',
-};
-
-const button = {
- backgroundColor: '#007ee6',
- borderRadius: '4px',
- color: '#fff',
- fontFamily: "'Open Sans', 'Helvetica Neue', Arial",
- fontSize: '15px',
- textDecoration: 'none',
- textAlign: 'center' as const,
- display: 'block',
- width: '210px',
- padding: '14px 7px',
-};
-
-const anchor = {
- textDecoration: 'underline',
-};
+export default DropboxResetPasswordEmail;
diff --git a/apps/demo/emails/reset-password/twitch-reset-password.tsx b/apps/demo/emails/reset-password/twitch-reset-password.tsx
index 60eb156c32..de99963778 100644
--- a/apps/demo/emails/reset-password/twitch-reset-password.tsx
+++ b/apps/demo/emails/reset-password/twitch-reset-password.tsx
@@ -9,8 +9,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface TwitchResetPasswordEmailProps {
username?: string;
@@ -33,83 +35,85 @@ export const TwitchResetPasswordEmail = ({
return (
- You updated the password for your Twitch account
-
-
-
-
-
+
+
+ You updated the password for your Twitch account
+
+
+
+
+
+
+ Hi {username},
+
+ You updated the password for your Twitch account on{' '}
+ {formattedDate}. If this was you, then no further action is
+ required.
+
+
+ However if you did NOT perform this password change, please{' '}
+
+ reset your account password
+ {' '}
+ immediately.
+
+
+ Remember to use a password that is both strong and unique to
+ your Twitch account. To learn more about how to create a strong
+ and unique password,{' '}
+
+ click here.
+
+
+
+ Still have questions? Please contact{' '}
+
+ Twitch Support
+
+
+
+ Thanks,
+
+ Twitch Support Team
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+ © 2022 Twitch, All Rights Reserved
+ 350 Bush Street, 2nd Floor, San Francisco, CA, 94104 - USA
+
-
- Hi {username},
-
- You updated the password for your Twitch account on{' '}
- {formattedDate}. If this was you, then no further action is
- required.
-
-
- However if you did NOT perform this password change, please{' '}
-
- reset your account password
- {' '}
- immediately.
-
-
- Remember to use a password that is both strong and unique to your
- Twitch account. To learn more about how to create a strong and
- unique password,{' '}
-
- click here.
-
-
-
- Still have questions? Please contact{' '}
-
- Twitch Support
-
-
-
- Thanks,
-
- Twitch Support Team
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- © 2022 Twitch, All Rights Reserved
- 350 Bush Street, 2nd Floor, San Francisco, CA, 94104 - USA
-
-
-
-
+
+
);
};
@@ -120,56 +124,3 @@ TwitchResetPasswordEmail.PreviewProps = {
} as TwitchResetPasswordEmailProps;
export default TwitchResetPasswordEmail;
-
-const fontFamily = 'HelveticaNeue,Helvetica,Arial,sans-serif';
-
-const main = {
- backgroundColor: '#efeef1',
- fontFamily,
-};
-
-const paragraph = {
- lineHeight: 1.5,
- fontSize: 14,
-};
-
-const container = {
- maxWidth: '580px',
- margin: '30px auto',
- backgroundColor: '#ffffff',
-};
-
-const footer = {
- maxWidth: '580px',
- margin: '0 auto',
-};
-
-const content = {
- padding: '5px 20px 10px 20px',
-};
-
-const logo = {
- padding: 30,
-};
-
-const logoImg = {
- margin: '0 auto',
-};
-
-const sectionsBorders = {
- width: '100%',
-};
-
-const sectionBorder = {
- borderBottom: '1px solid rgb(238,238,238)',
- width: '249px',
-};
-
-const sectionCenter = {
- borderBottom: '1px solid rgb(145,71,255)',
- width: '102px',
-};
-
-const link = {
- textDecoration: 'underline',
-};
diff --git a/apps/demo/emails/reviews/airbnb-review.tsx b/apps/demo/emails/reviews/airbnb-review.tsx
index 52381120bd..d345f8d646 100644
--- a/apps/demo/emails/reviews/airbnb-review.tsx
+++ b/apps/demo/emails/reviews/airbnb-review.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface AirbnbReviewEmailProps {
authorName?: string;
@@ -34,78 +36,100 @@ export const AirbnbReviewEmail = ({
- {previewText}
-
-
-
-
-
-
-
-
-
- Here's what {authorName} wrote
- {reviewText}
-
- Now that the review period is over, we’ve posted {authorName}
- ’s review to your Airbnb profile.
-
-
- While it’s too late to write a review of your own, you can send
- your feedback to {authorName} using your Airbnb message thread.
-
-
-
- Send My Feedback
-
-
-
-
-
-
-
-
-
- Common questions
-
-
-
- How do reviews work?
-
-
-
-
- How do star ratings work?
+
+
+ {previewText}
+
+
+
+
+
+
+
+
+
+
+ Here's what {authorName} wrote
+
+
+ {reviewText}
+
+
+ Now that the review period is over, we've posted {authorName}
+ 's review to your Airbnb profile.
+
+
+ While it's too late to write a review of your own, you can
+ send your feedback to {authorName} using your Airbnb message
+ thread.
+
+
+
+ Send My Feedback
+
+
+
+
+
+
+
+
+
+ Common questions
+
+
+
+ How do reviews work?
+
+
+
+
+ How do star ratings work?
+
+
+
+
+ Can I leave a review after 14 days?
+
+
+
+
+ Airbnb, Inc., 888 Brannan St, San Francisco, CA 94103
+
+
+ Report unsafe behavior
-
-
-
- Can I leave a review after 14 days?
-
-
-
-
- Airbnb, Inc., 888 Brannan St, San Francisco, CA 94103
-
-
- Report unsafe behavior
-
-
-
-
-
+
+
+
+
+
);
};
@@ -119,77 +143,6 @@ AirbnbReviewEmail.PreviewProps = {
host!”`,
} as AirbnbReviewEmailProps;
-export default AirbnbReviewEmail;
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const container = {
- margin: '0 auto',
- padding: '20px 0 48px',
- width: '580px',
- maxWidth: '100%',
-};
-
-const userImage = {
- margin: '0 auto',
- marginBottom: '16px',
- borderRadius: '50%',
-};
-
-const heading = {
- fontSize: '32px',
- lineHeight: '1.3',
- fontWeight: '700',
- color: '#484848',
-};
-
-const paragraph = {
- fontSize: '18px',
- lineHeight: '1.4',
- color: '#484848',
-};
-
-const review = {
- ...paragraph,
- padding: '24px',
- backgroundColor: '#f2f3f3',
- borderRadius: '4px',
-};
-
-const button = {
- backgroundColor: '#ff5a5f',
- borderRadius: '3px',
- color: '#fff',
- fontSize: '18px',
- padding: '19px 30px',
- textDecoration: 'none',
- textAlign: 'center' as const,
- display: 'block',
-};
-
-const link = {
- ...paragraph,
- color: '#ff5a5f',
- display: 'block',
-};
-
-const reportLink = {
- fontSize: '14px',
- color: '#9ca299',
- textDecoration: 'underline',
-};
+AirbnbReviewEmail.tailwindConfig = tailwindConfig;
-const hr = {
- borderColor: '#cccccc',
- margin: '20px 0',
-};
-
-const footer = {
- color: '#9ca299',
- fontSize: '14px',
- marginBottom: '10px',
-};
+export default AirbnbReviewEmail;
diff --git a/apps/demo/emails/reviews/amazon-review.tsx b/apps/demo/emails/reviews/amazon-review.tsx
index 08fb4a2ffb..02da9acef4 100644
--- a/apps/demo/emails/reviews/amazon-review.tsx
+++ b/apps/demo/emails/reviews/amazon-review.tsx
@@ -10,8 +10,10 @@ import {
Preview,
Row,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface AmazonReviewEmailProps {
titleText?: string;
@@ -38,204 +40,158 @@ export const AmazonReviewEmail = ({
- Amazon Review
-
-
-
-
-
+
+
+ Amazon Review
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {titleText}
+
+ {reviewText}
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {titleText}
- {reviewText}
-
-
-
-
-
-
-
-
-
-
-
-
- 14 Habits of Highly Productive Developers (English Edition)
-
- Start with rating this product
-
- {reviwStars.map((star, index) => (
+
+
+
+
+ 14 Habits of Highly Productive Developers (English Edition)
+
+ Start with rating this product
+
+ {reviwStars.map((star, index) => (
+
+ ))}
+
+
+ Your reviews will be posted on Amazon using your public
+ name.
+ Check your public name.
+
+
+
+
+
+
+
+
+
+ How about evaluating a previous purchase?{' '}
+
+ View more
+
+
+
+
+
+
+
+
+
+
+
- ))}
-
-
- Your reviews will be posted on Amazon using your public name.
- Check your public name.
+
+
+
+ {socialMediaIcons.map((src, index) => (
+
+ ))}
+
+
+
+
+
+
+
+ Customer reviews must adhere to the{' '}
+
+ Community Guidelines
+ {' '}
+ .
-
-
-
-
-
-
-
-
- How about evaluating a previous purchase?{' '}
- View more
+
+ We hope this message was helpful to you. However, if you
+ prefer not to receive this type of communication from{' '}
+
+ Amazon.com
+ {' '}
+ at{' '}
+
+ alanturing@gmail.com{' '}
+
+ ,{' '}
+
+ click here
+ {' '}
+ .
-
-
-
-
-
-
-
-
-
-
-
-
-
- {socialMediaIcons.map((src, index) => (
-
- ))}
-
-
-
-
-
-
-
- Customer reviews must adhere to the{' '}
- Community Guidelines .
-
-
- We hope this message was helpful to you. However, if you prefer
- not to receive this type of communication from{' '}
- Amazon.com at{' '}
- alanturing@gmail.com ,{' '}
- click here .
-
-
- Please note that product prices and availability are subject to
- change.
-
-
- © 2023 Amazon.com, Inc. or its affiliates. Amazon and all
- associated marks are trademarks of Amazon.com, Inc. or its
- affiliates.
-
- Reference: 706784740
-
-
-
-
+
+ Please note that product prices and availability are subject
+ to change.
+
+
+ © 2023 Amazon.com, Inc. or its affiliates. Amazon and all
+ associated marks are trademarks of Amazon.com, Inc. or its
+ affiliates.
+
+
+ Reference: 706784740
+
+
+
+
+
+
);
};
-export default AmazonReviewEmail;
-
-const main = {
- fontFamily: 'Ember,Helvetica,Arial,sans-seri',
- backgroundColor: '#ffffff',
-};
-
-const container = {
- borderTop: '4px solid #FF9900',
- margin: '0 auto',
- padding: '20px',
- width: '640px',
-};
-
-const title = {
- color: '#232f3e',
- fontSize: '36px',
- lineHeight: '38px',
- fontWeight: '400',
- margin: '20px 0',
-};
-
-const ratingContent = {
- paddingLeft: '30px',
-};
+AmazonReviewEmail.tailwindConfig = tailwindConfig;
-const rating = {
- display: 'inline-block',
-};
-
-const previewPurchase = {
- background: '#008296',
- color: '#ffffff',
- padding: '8px 0',
-};
-
-const previewPurchaseLink = {
- color: '#ffffff',
- textDecoration: 'underline',
- cursor: 'pointer',
-};
-
-const socialMedia = {
- display: 'inline-block',
- marginLeft: '10px',
-};
-
-const communityLink = {
- color: '#666666',
- textDecoration: 'underline',
- cursor: 'pointer',
-};
-
-const clickHereLink = {
- color: '#999999',
- textDecoration: 'underline',
- cursor: 'pointer',
-};
-
-const urlLink = {
- color: '#1155cc',
- textDecoration: 'underline',
- cursor: 'pointer',
-};
-
-const footerText = {
- fontSize: '10px',
- color: '#666666',
- margin: '8px 0',
-};
+export default AmazonReviewEmail;
diff --git a/apps/demo/emails/tailwind.config.ts b/apps/demo/emails/tailwind.config.ts
new file mode 100644
index 0000000000..6568f85368
--- /dev/null
+++ b/apps/demo/emails/tailwind.config.ts
@@ -0,0 +1,93 @@
+import { pixelBasedPreset, type TailwindConfig } from '@react-email/components';
+
+export default {
+ presets: [pixelBasedPreset],
+ theme: {
+ fontFamily: {
+ amazon: ['Ember', 'Helvetica', 'Arial', 'sans-serif'],
+ koala: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'Segoe UI',
+ 'Roboto',
+ 'Oxygen-Sans',
+ 'Ubuntu',
+ 'Cantarell',
+ 'Helvetica Neue',
+ 'sans-serif',
+ ],
+ raycast:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+ slack:
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
+ twitch: ['HelveticaNeue', 'Helvetica', 'Arial', 'sans-serif'],
+ 'stack-overflow': ['Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'],
+ 'stack-overflow-mono': 'Consolas,monospace',
+ github:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
+ yelp: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ 'Oxygen-Sans',
+ 'Ubuntu',
+ 'Cantarell',
+ '"Helvetica Neue"',
+ 'sans-serif',
+ ],
+ nike: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ 'Oxygen-Sans',
+ 'Ubuntu',
+ 'Cantarell',
+ '"Helvetica Neue"',
+ 'sans-serif',
+ ],
+ linear:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+ stripe: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ '"Helvetica Neue"',
+ 'Ubuntu',
+ 'sans-serif',
+ ],
+ notion:
+ "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
+ aws: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
+ airbnb: [
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ '"Segoe UI"',
+ 'Roboto',
+ 'Oxygen-Sans',
+ 'Ubuntu',
+ 'Cantarell',
+ '"Helvetica Neue"',
+ 'sans-serif',
+ ],
+ apple: ['"Helvetica Neue"', 'Helvetica', 'Arial', 'sans-serif'],
+ dropbox: [
+ 'Open Sans',
+ 'HelveticaNeue-Light',
+ 'Helvetica Neue Light',
+ 'Helvetica Neue',
+ 'Helvetica',
+ 'Arial',
+ 'Lucida Grande',
+ 'sans-serif',
+ ],
+ 'dropbox-sans': ['Open Sans', 'Helvetica Neue', 'Arial'],
+ codepen: '"Google Sans",Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
+ plaid: 'HelveticaNeue,Helvetica,Arial,sans-serif',
+ google:
+ '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
+ },
+ },
+} satisfies TailwindConfig;
diff --git a/apps/demo/emails/welcome/koala-welcome.tsx b/apps/demo/emails/welcome/koala-welcome.tsx
index 5ec1ebe0bc..1267a887ae 100644
--- a/apps/demo/emails/welcome/koala-welcome.tsx
+++ b/apps/demo/emails/welcome/koala-welcome.tsx
@@ -8,8 +8,10 @@ import {
Img,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
interface KoalaWelcomeEmailProps {
userFirstname: string;
@@ -24,39 +26,47 @@ export const KoalaWelcomeEmail = ({
}: KoalaWelcomeEmailProps) => (
-
- The sales intelligence platform that helps you uncover qualified leads.
-
-
-
- Hi {userFirstname},
-
- Welcome to Koala, the sales intelligence platform that helps you
- uncover qualified leads and close deals faster.
-
-
-
- Best,
-
- The Koala team
-
-
-
- 470 Noor Ave STE B #1148, South San Francisco, CA 94080
-
-
-
+
+
+
+ The sales intelligence platform that helps you uncover qualified
+ leads.
+
+
+
+
+ Hi {userFirstname},
+
+
+ Welcome to Koala, the sales intelligence platform that helps you
+ uncover qualified leads and close deals faster.
+
+
+
+ Best,
+
+ The Koala team
+
+
+
+ 470 Noor Ave STE B #1148, South San Francisco, CA 94080
+
+
+
+
);
@@ -65,48 +75,3 @@ KoalaWelcomeEmail.PreviewProps = {
} as KoalaWelcomeEmailProps;
export default KoalaWelcomeEmail;
-
-const main = {
- backgroundColor: '#ffffff',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif',
-};
-
-const container = {
- margin: '0 auto',
- padding: '20px 0 48px',
-};
-
-const logo = {
- margin: '0 auto',
-};
-
-const paragraph = {
- fontSize: '16px',
- lineHeight: '26px',
-};
-
-const btnContainer = {
- textAlign: 'center' as const,
-};
-
-const button = {
- backgroundColor: '#5F51E8',
- borderRadius: '3px',
- color: '#fff',
- fontSize: '16px',
- textDecoration: 'none',
- textAlign: 'center' as const,
- display: 'block',
- padding: '12px',
-};
-
-const hr = {
- borderColor: '#cccccc',
- margin: '20px 0',
-};
-
-const footer = {
- color: '#8898aa',
- fontSize: '12px',
-};
diff --git a/apps/demo/emails/welcome/stripe-welcome.tsx b/apps/demo/emails/welcome/stripe-welcome.tsx
index 9422531e01..1ceac00d5d 100644
--- a/apps/demo/emails/welcome/stripe-welcome.tsx
+++ b/apps/demo/emails/welcome/stripe-welcome.tsx
@@ -9,8 +9,10 @@ import {
Link,
Preview,
Section,
+ Tailwind,
Text,
} from '@react-email/components';
+import tailwindConfig from '../tailwind.config';
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@@ -19,138 +21,98 @@ const baseUrl = process.env.VERCEL_URL
export const StripeWelcomeEmail = () => (
- You're now ready to make live transactions with Stripe!
-
-
-
-
-
- Thanks for submitting your account information. You're now ready to
- make live transactions with Stripe!
-
-
- You can view your payments and a variety of other information about
- your account right from your dashboard.
-
-
- View your Stripe Dashboard
-
-
-
- If you haven't finished your integration, you might find our{' '}
-
+
+
+ You're now ready to make live transactions with Stripe!
+
+
+
+
+
+
+ Thanks for submitting your account information. You're now ready
+ to make live transactions with Stripe!
+
+
+ You can view your payments and a variety of other information
+ about your account right from your dashboard.
+
+
- docs
- {' '}
- handy.
-
-
- Once you're ready to start accepting payments, you'll just need to
- use your live{' '}
-
- API keys
- {' '}
- instead of your test API keys. Your account can simultaneously be
- used for both test and live requests, so you can continue testing
- while accepting live payments. Check out our{' '}
-
- tutorial about account basics
-
- .
-
-
- Finally, we've put together a{' '}
-
- quick checklist
- {' '}
- to ensure your website conforms to card network standards.
-
-
- We'll be here to help you with any step along the way. You can find
- answers to most questions and get in touch with us on our{' '}
-
- support site
-
- .
-
- — The Stripe team
-
-
- Stripe, 354 Oyster Point Blvd, South San Francisco, CA 94080
-
-
-
-
+ View your Stripe Dashboard
+
+
+
+ If you haven't finished your integration, you might find our{' '}
+
+ docs
+ {' '}
+ handy.
+
+
+ Once you're ready to start accepting payments, you'll just need to
+ use your live{' '}
+
+ API keys
+ {' '}
+ instead of your test API keys. Your account can simultaneously be
+ used for both test and live requests, so you can continue testing
+ while accepting live payments. Check out our{' '}
+
+ tutorial about account basics
+
+ .
+
+
+ Finally, we've put together a{' '}
+
+ quick checklist
+ {' '}
+ to ensure your website conforms to card network standards.
+
+
+ We'll be here to help you with any step along the way. You can
+ find answers to most questions and get in touch with us on our{' '}
+
+ support site
+
+ .
+
+
+ — The Stripe team
+
+
+
+ Stripe, 354 Oyster Point Blvd, South San Francisco, CA 94080
+
+
+
+
+
);
export default StripeWelcomeEmail;
-
-const main = {
- backgroundColor: '#f6f9fc',
- fontFamily:
- '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
-};
-
-const container = {
- backgroundColor: '#ffffff',
- margin: '0 auto',
- padding: '20px 0 48px',
- marginBottom: '64px',
-};
-
-const box = {
- padding: '0 48px',
-};
-
-const hr = {
- borderColor: '#e6ebf1',
- margin: '20px 0',
-};
-
-const paragraph = {
- color: '#525f7f',
-
- fontSize: '16px',
- lineHeight: '24px',
- textAlign: 'left' as const,
-};
-
-const anchor = {
- color: '#556cd6',
-};
-
-const button = {
- backgroundColor: '#656ee8',
- borderRadius: '5px',
- color: '#fff',
- fontSize: '16px',
- fontWeight: 'bold',
- textDecoration: 'none',
- textAlign: 'center' as const,
- display: 'block',
- padding: '10px',
-};
-
-const footer = {
- color: '#8898aa',
- fontSize: '12px',
- lineHeight: '16px',
-};
diff --git a/apps/demo/package.json b/apps/demo/package.json
index 79ff536b09..8469564f0f 100644
--- a/apps/demo/package.json
+++ b/apps/demo/package.json
@@ -10,14 +10,14 @@
},
"dependencies": {
"@react-email/components": "workspace:*",
+ "email-dev": "workspace:*",
"react": "^19",
- "react-dom": "^19",
- "email-dev": "workspace:*"
+ "react-dom": "^19"
},
"devDependencies": {
"@react-email/preview-server": "workspace:*",
- "next": "^15.3.2",
"@types/react": "^19",
- "@types/react-dom": "^19"
+ "@types/react-dom": "^19",
+ "next": "^15.3.2"
}
}
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 109fc2eef2..bc054b60e0 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -1,5 +1,16 @@
# @react-email/components
+## 1.0.0-canary.0
+
+### Major Changes
+
+- 9360e39: tailwind: update to using tailwindcss@v4
+
+### Patch Changes
+
+- Updated dependencies [9360e39]
+ - @react-email/tailwind@2.0.0-canary.0
+
## 0.5.7
### Patch Changes
diff --git a/packages/components/package.json b/packages/components/package.json
index 059c8d9742..9fbc0ca175 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-email/components",
- "version": "0.5.7",
+ "version": "1.0.0-canary.0",
"description": "A collection of all components React Email.",
"sideEffects": false,
"main": "./dist/index.js",
@@ -58,7 +58,7 @@
"@react-email/render": "workspace:1.4.0",
"@react-email/row": "workspace:0.0.12",
"@react-email/section": "workspace:0.0.16",
- "@react-email/tailwind": "workspace:1.2.2",
+ "@react-email/tailwind": "workspace:2.0.0-canary.0",
"@react-email/text": "workspace:0.1.5"
},
"peerDependencies": {
diff --git a/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap b/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap
index a6e818a9d9..3886d5c63b 100644
--- a/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap
+++ b/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap
@@ -12,8 +12,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = `
-
+
with a demo email template 1`] = `
+ style='margin-right:auto;margin-left:auto;margin-bottom:auto;margin-top:auto;background-color:rgb(255,255,255);padding-right:8px;padding-left:8px;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"'>
@@ -40,7 +39,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = `
cellpadding="0"
cellspacing="0"
role="presentation"
- style="margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;max-width:465px;border-radius:0.25rem;border-width:1px;border-color:rgb(234,234,234);border-style:solid;padding:20px">
+ style="max-width:465px;margin-right:auto;margin-left:auto;margin-bottom:40px;margin-top:40px;border-radius:0.25rem;border-style:solid;border-width:1px;border-color:rgb(234,234,234);padding:20px">
@@ -59,26 +58,26 @@ exports[`getEmailComponent() > with a demo email template 1`] = `
alt="Vercel Logo"
height="37"
src="/static/vercel-logo.png"
- style="margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;display:block;outline:none;border:none;text-decoration:none"
+ style="display:block;outline:none;border:none;text-decoration:none;margin-right:auto;margin-left:auto;margin-bottom:0;margin-top:0"
width="40" />
+ style="margin-right:0;margin-left:0;margin-bottom:30px;margin-top:30px;padding:0;text-align:center;font-weight:400;font-size:24px;color:rgb(0,0,0)">
Join Enigma on Vercel
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
Hello
alanturing,
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
Alan (alan.turing@example.com ) has invited you to the Enigma team on
@@ -110,7 +109,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = `
alt="alanturing's profile picture"
height="64"
src="/static/vercel-user.png"
- style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none"
+ style="display:block;outline:none;border:none;text-decoration:none;border-radius:9999px"
width="64" />
with a demo email template 1`] = `
alt="Enigma team logo"
height="64"
src="/static/vercel-team.png"
- style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none"
+ style="display:block;outline:none;border:none;text-decoration:none;border-radius:9999px"
width="64" />
@@ -153,7 +152,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = `
with a demo email template 1`] = `
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
or copy and paste this URL into your browser:
https://vercel.com
+ style="width:100%;border:none;border-top:1px solid #eaeaea;margin-right:0;margin-left:0;margin-bottom:26px;margin-top:26px;border-style:solid;border-width:1px;border-color:rgb(234,234,234)" />
+ style="font-size:12px;line-height:24px;color:rgb(102,102,102);margin-top:16px;margin-bottom:16px">
This invitation was intended for
alanturing . This
invite was sent from
diff --git a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap
index 615acfb5ad..7e4c1a61e7 100644
--- a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap
+++ b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap
@@ -10,8 +10,7 @@ exports[`email export 1`] = `
-
+
+ style='margin-right:auto;margin-left:auto;margin-bottom:auto;margin-top:auto;background-color:rgb(255,255,255);padding-right:8px;padding-left:8px;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"'>
@@ -38,7 +37,7 @@ exports[`email export 1`] = `
cellpadding="0"
cellspacing="0"
role="presentation"
- style="margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;max-width:465px;border-radius:0.25rem;border-width:1px;border-color:rgb(234,234,234);border-style:solid;padding:20px">
+ style="max-width:465px;margin-right:auto;margin-left:auto;margin-bottom:40px;margin-top:40px;border-radius:0.25rem;border-style:solid;border-width:1px;border-color:rgb(234,234,234);padding:20px">
@@ -57,26 +56,26 @@ exports[`email export 1`] = `
alt="Vercel Logo"
height="37"
src="/static/vercel-logo.png"
- style="margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;display:block;outline:none;border:none;text-decoration:none"
+ style="display:block;outline:none;border:none;text-decoration:none;margin-right:auto;margin-left:auto;margin-bottom:0;margin-top:0"
width="40" />
+ style="margin-right:0;margin-left:0;margin-bottom:30px;margin-top:30px;padding:0;text-align:center;font-weight:400;font-size:24px;color:rgb(0,0,0)">
Join on Vercel
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
Hello
,
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
( ) has invited you to the team on
Vercel .
@@ -106,7 +105,7 @@ exports[`email export 1`] = `
@@ -147,7 +146,7 @@ exports[`email export 1`] = `
+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px">
or copy and paste this URL into your browser:
+ style="width:100%;border:none;border-top:1px solid #eaeaea;margin-right:0;margin-left:0;margin-bottom:26px;margin-top:26px;border-style:solid;border-width:1px;border-color:rgb(234,234,234)" />
+ style="font-size:12px;line-height:24px;color:rgb(102,102,102);margin-top:16px;margin-bottom:16px">
This invitation was intended for
. This invite was
sent from
diff --git a/packages/render/src/shared/options.ts b/packages/render/src/shared/options.ts
index 3cc9b672ba..c1f330f6db 100644
--- a/packages/render/src/shared/options.ts
+++ b/packages/render/src/shared/options.ts
@@ -1,20 +1,31 @@
import type { HtmlToTextOptions } from 'html-to-text';
-import type { pretty } from './utils/pretty';
import type { toPlainText } from './utils/to-plain-text';
export type Options = {
/**
- * @deprecated use {@link pretty} instead
+ * @deprecated This option will be removed in a future major release.
+ * Please format the rendered HTML yourself instead of relying on this option.
+ *
+ * @example
+ * ```ts
+ * // Render the email to HTML
+ * const html = await render(email);
+ *
+ * // Format the HTML using your preferred formatter (e.g. Prettier)
+ * const formattedHtml = await format(html);
+ * ```
+ *
+ * @see https://github.com/resend/react-email/issues/2426
*/
pretty?: boolean;
} & (
- | {
+ | {
/**
* @deprecated use {@link toPlainText} instead
*/
plainText?: false;
}
- | {
+ | {
/**
* @deprecated use {@link toPlainText} instead
*/
@@ -27,4 +38,4 @@ export type Options = {
*/
htmlToTextOptions?: HtmlToTextOptions;
}
-);
+ );
diff --git a/packages/render/src/shared/utils/pretty.ts b/packages/render/src/shared/utils/pretty.ts
index 63887c1a57..7bc47af7d2 100644
--- a/packages/render/src/shared/utils/pretty.ts
+++ b/packages/render/src/shared/utils/pretty.ts
@@ -92,6 +92,21 @@ const defaults: Options = {
parser: 'html',
};
+/**
+ * @deprecated This function will be removed in a future major release.
+ * Please format the rendered HTML yourself instead of relying on this function.
+ *
+ * @example
+ * ```ts
+ * // Render the email to HTML
+ * const html = await render(email);
+ *
+ * // Format the HTML using your preferred formatter (e.g. Prettier)
+ * const formattedHtml = await format(html);
+ * ```
+ *
+ * @see https://github.com/resend/react-email/issues/2426
+ */
export const pretty = (str: string, options: Options = {}) => {
return format(str.replaceAll('\0', ''), {
...defaults,
diff --git a/packages/tailwind/CHANGELOG.md b/packages/tailwind/CHANGELOG.md
index c5b4772b9f..842ec54e5e 100644
--- a/packages/tailwind/CHANGELOG.md
+++ b/packages/tailwind/CHANGELOG.md
@@ -1,5 +1,11 @@
# @react-email/tailwind
+## 2.0.0-canary.0
+
+### Major Changes
+
+- 9360e39: update to using tailwindcss@v4
+
## 1.2.2
### Patch Changes
diff --git a/packages/tailwind/copy-tailwind-types.mjs b/packages/tailwind/copy-tailwind-types.mjs
deleted file mode 100644
index 6fe7008614..0000000000
--- a/packages/tailwind/copy-tailwind-types.mjs
+++ /dev/null
@@ -1,13 +0,0 @@
-import { promises as fs } from 'node:fs';
-import path from 'node:path';
-import url from 'node:url';
-
-const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
-
-await fs.cp(
- path.resolve(__dirname, './node_modules/tailwindcss/types'),
- path.resolve(__dirname, './dist/tailwindcss'),
- {
- recursive: true,
- },
-);
diff --git a/packages/tailwind/package.json b/packages/tailwind/package.json
index 43ed1c5e28..45dbd8249e 100644
--- a/packages/tailwind/package.json
+++ b/packages/tailwind/package.json
@@ -1,6 +1,6 @@
{
"name": "@react-email/tailwind",
- "version": "1.2.2",
+ "version": "2.0.0-canary.0",
"description": "A React component to wrap emails with Tailwind CSS",
"sideEffects": false,
"main": "./dist/index.js",
@@ -23,8 +23,8 @@
},
"license": "MIT",
"scripts": {
- "build": "tsc && cross-env NODE_ENV=production vite build --mode production && node ./copy-tailwind-types.mjs",
- "build:watch": "vite build --watch",
+ "build": "tsdown",
+ "build:watch": "tsdown --watch",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest"
@@ -43,7 +43,50 @@
"node": ">=18.0.0"
},
"peerDependencies": {
- "react": "^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^18.0 || ^19.0 || ^19.0.0-rc",
+ "@react-email/body": "workspace:*",
+ "@react-email/button": "workspace:*",
+ "@react-email/code-block": "workspace:*",
+ "@react-email/code-inline": "workspace:*",
+ "@react-email/container": "workspace:*",
+ "@react-email/heading": "workspace:*",
+ "@react-email/hr": "workspace:*",
+ "@react-email/img": "workspace:*",
+ "@react-email/link": "workspace:*",
+ "@react-email/preview": "workspace:*",
+ "@react-email/text": "workspace:*"
+ },
+ "peerDependenciesMeta": {
+ "@react-email/button": {
+ "optional": true
+ },
+ "@react-email/body": {
+ "optional": true
+ },
+ "@react-email/code-block": {
+ "optional": true
+ },
+ "@react-email/code-inline": {
+ "optional": true
+ },
+ "@react-email/container": {
+ "optional": true
+ },
+ "@react-email/heading": {
+ "optional": true
+ },
+ "@react-email/hr": {
+ "optional": true
+ },
+ "@react-email/img": {
+ "optional": true
+ },
+ "@react-email/link": {
+ "optional": true
+ },
+ "@react-email/preview": {
+ "optional": true
+ }
},
"devDependencies": {
"@react-email/button": "workspace:^",
@@ -54,14 +97,12 @@
"@react-email/link": "workspace:*",
"@react-email/render": "workspace:*",
"@responsive-email/react-email": "0.0.4",
+ "@types/css-tree": "2.3.10",
"@types/shelljs": "0.8.15",
"@vitejs/plugin-react": "4.4.1",
- "cross-env": "10.0.0",
- "postcss": "8.5.3",
- "postcss-selector-parser": "7.1.0",
+ "css-tree": "3.1.0",
"react-dom": "^19",
"shelljs": "0.9.2",
- "tailwindcss": "3.4.10",
"tsconfig": "workspace:*",
"typescript": "5.8.3",
"vite": "6.3.6",
@@ -70,5 +111,8 @@
},
"publishConfig": {
"access": "public"
+ },
+ "dependencies": {
+ "tailwindcss": "4.1.12"
}
}
diff --git a/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap b/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap
index 0bdcd1b93d..c6edea3134 100644
--- a/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap
+++ b/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap
@@ -1,52 +1,225 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`Custom plugins config > uses custom plugins 1`] = `"
"`;
+exports[`Tailwind component > 1`] = `"Testing button Testing"`;
-exports[`Custom plugins config > uses custom plugins with responsive styles 1`] = `"
"`;
+exports[`Tailwind component > allows for complex children manipulation 1`] = `"This is the second column
"`;
-exports[`Custom theme config > uses custom border radius 1`] = `"
"`;
+exports[`Tailwind component > does not override inline styles with Tailwind styles 1`] = `"
"`;
-exports[`Custom theme config > uses custom colors 1`] = `"
"`;
+exports[`Tailwind component > doesn't generate styles from text 1`] = `"container bg-red-500 bg-blue-300"`;
-exports[`Custom theme config > uses custom fonts 1`] = `"
"`;
+exports[`Tailwind component > overrides component styles with Tailwind styles 1`] = `" "`;
-exports[`Custom theme config > uses custom spacing 1`] = `"
"`;
+exports[`Tailwind component > preserves mso styles 1`] = `
+"
+
+
+
+
+
+
+
+
+
+"
+`;
-exports[`Custom theme config > uses custom text alignment 1`] = `"
"`;
+exports[`Tailwind component > recognizes custom responsive screen 1`] = `
+"
+
+
+
+
+
+
+
+
+ Test
+
+ Test
+
+
+"
+`;
-exports[`Tailwind component > 1`] = `"Testing button Testing"`;
+exports[`Tailwind component > renders children with inline Tailwind styles 1`] = `"
"`;
-exports[`Tailwind component > allows for complex children manipulation 1`] = `"This is the second column
"`;
+exports[`Tailwind component > uses background image 1`] = `"
"`;
-exports[`Tailwind component > does not override inline styles with Tailwind styles 1`] = `"
"`;
+exports[`Tailwind component > with custom plugins config > supports custom plugins 1`] = `
+"
+
+
+
+"
+`;
-exports[`Tailwind component > it should not generate styles from text 1`] = `"container bg-red-500 bg-blue-300"`;
+exports[`Tailwind component > with custom plugins config > supports custom plugins with responsive styles 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+"
+`;
-exports[`Tailwind component > preserves mso styles 1`] = `"
"`;
+exports[`Tailwind component > with custom theme config > supports custom border radius 1`] = `
+"
+
+
+
+"
+`;
-exports[`Tailwind component > recognizes custom responsive screen 1`] = `"Test
Test
"`;
+exports[`Tailwind component > with custom theme config > supports custom colors 1`] = `
+"
+
+
+
+"
+`;
-exports[`Tailwind component > uses background image 1`] = `"
"`;
+exports[`Tailwind component > with custom theme config > supports custom fonts 1`] = `
+"
+
+
+
+
+"
+`;
-exports[`Tailwind component > warns about safelist not being supported 1`] = `
+exports[`Tailwind component > with custom theme config > supports custom spacing 1`] = `
+"
+
+
+
+"
+`;
+
+exports[`Tailwind component > with custom theme config > supports custom text alignment 1`] = `
+"
+
+
+
+"
+`;
+
+exports[`Tailwind component > with non-inlinable styles > adds css to and keep class names 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
+exports[`Tailwind component > with non-inlinable styles > persists existing elements 1`] = `
+"
+
+
+
+
+
+
+
+
+
+
+
+
+"
+`;
+
+exports[`Tailwind component > with non-inlinable styles > throws an error when used without a 1`] = `
+[Error: You are trying to use the following Tailwind classes that cannot be inlined: sm:bg-red-500.
+For the media queries to work properly on rendering, they need to be added into a
+
+
+
+
+
+
+"
+`;
+
+exports[`Tailwind component > with non-inlinable styles > works with arbitrarily deep (in the React tree) elements 2`] = `"
"`;
+
+exports[`Tailwind component > with non-inlinable styles > works with relatively complex media query utilities 1`] = `
"
-
-
- Click me
-
+I am some text
+
"
`;
-exports[`Tailwind component > works properly with 'no-underline' 1`] = `"or copy and paste this URL into your browser: https://react.email
or copy and paste this URL into your browser: https://react.email
"`;
+exports[`Tailwind component > works properly with 'no-underline' 1`] = `"or copy and paste this URL into your browser: https://react.email
or copy and paste this URL into your browser: https://react.email
"`;
exports[`Tailwind component > works with Heading component 1`] = `"HelloMy testing heading friends"`;
@@ -57,7 +230,7 @@ exports[`Tailwind component > works with blocklist 1`] = `
@@ -67,63 +240,38 @@ exports[`Tailwind component > works with blocklist 1`] = `
"
`;
-exports[`Tailwind component > works with calc() with + sign 1`] = `""`;
-
-exports[`Tailwind component > works with class manipulation done on components 1`] = `"
"`;
-
-exports[`Tailwind component > works with components that return children 1`] = `"Hello world
"`;
-
-exports[`Tailwind component > works with components that use React.forwardRef 1`] = `"Hello world
"`;
-
-exports[`Tailwind component > works with custom components with fragment at the root 1`] = `"Hello world
"`;
-
-exports[`non-inlinable styles > adds css to and keeps class names 1`] = `"
"`;
-
-exports[`non-inlinable styles > does not have duplicate media queries 1`] = `
+exports[`Tailwind component > works with calc() with + sign 1`] = `
"
-
-
-
-
-
-
+
+
"
`;
-exports[`non-inlinable styles > persists existing elements 1`] = `"
"`;
-
-exports[`non-inlinable styles > throws an error when used without a
"`;
-
-exports[`non-inlinable styles > works with arbitrarily deep (in the React tree) elements 2`] = `"
"`;
-
-exports[`non-inlinable styles > works with relatively complex media query utilities 1`] = `"I am some text
"`;
diff --git a/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap b/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap
deleted file mode 100644
index baebff2970..0000000000
--- a/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap
+++ /dev/null
@@ -1,16 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`useTailwind() 1`] = `
-".text-red-500 {
- color: rgb(239 68 68 / 1)
-}
- @media (min-width: 640px) {
- .sm\\:bg-blue-300 {
- background-color: rgb(147 197 253 / 1)
- }
-}
- .bg-slate-900 {
- background-color: rgb(15 23 42 / 1)
-}
-"
-`;
diff --git a/packages/tailwind/src/hooks/use-suspensed-promise.ts b/packages/tailwind/src/hooks/use-suspended-promise.ts
similarity index 100%
rename from packages/tailwind/src/hooks/use-suspensed-promise.ts
rename to packages/tailwind/src/hooks/use-suspended-promise.ts
diff --git a/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts b/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts
index a43199c247..2063a1ab49 100644
--- a/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts
+++ b/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts
@@ -1,5 +1,5 @@
/** biome-ignore-all lint/correctness/useHookAtTopLevel: function is not a React hook */
-import { useSuspensedPromise } from './use-suspensed-promise';
+import { useSuspensedPromise } from './use-suspended-promise';
describe('useSuspensedPromise', () => {
beforeEach(() => {});
diff --git a/packages/tailwind/src/tailwind.spec.tsx b/packages/tailwind/src/tailwind.spec.tsx
index cc05724cbd..292521e000 100644
--- a/packages/tailwind/src/tailwind.spec.tsx
+++ b/packages/tailwind/src/tailwind.spec.tsx
@@ -4,10 +4,9 @@ import { Heading } from '@react-email/heading';
import { Hr } from '@react-email/hr';
import { Html } from '@react-email/html';
import { Link } from '@react-email/link';
-import { render } from '@react-email/render';
+import { pretty, render } from '@react-email/render';
import { ResponsiveColumn, ResponsiveRow } from '@responsive-email/react-email';
import React from 'react';
-import { vi } from 'vitest';
import type { TailwindConfig } from '.';
import { Tailwind } from '.';
@@ -26,7 +25,11 @@ describe('Tailwind component', () => {
it('works with blocklist', async () => {
const actualOutput = await render(
-
+
@@ -40,23 +43,14 @@ describe('Tailwind component', () => {
expect(actualOutput).toMatchSnapshot();
});
- it('warns about safelist not being supported', async () => {
- const spy = vi.spyOn(console, 'warn');
-
- const actualOutput = await render(
-
-
-
-
- Click me
-
-
- ,
- { pretty: true },
- );
-
- expect(spy).toHaveBeenCalled();
- expect(actualOutput).toMatchSnapshot();
+ it('works with shadows', async () => {
+ expect(
+ await render(
+
+ shadow around here
+ ,
+ ).then(pretty),
+ ).toMatchSnapshot();
});
it('works with class manipulation done on components', async () => {
@@ -66,11 +60,9 @@ describe('Tailwind component', () => {
}) => {
expect(
props.style,
- 'Styles should be generated the same for a component',
- ).toEqual({
- color: 'rgb(96,165,250)',
- padding: '1rem',
- });
+ 'styles should not be generated for a component',
+ ).toBeUndefined();
+ expect(props.className).toBe('p-4 text-blue-400');
return (
{
expect(actualOutput).toMatchSnapshot();
});
- describe('Inline styles', () => {
- it('renders children with inline Tailwind styles', async () => {
- const actualOutput = await render(
-
-
- ,
- );
+ it('renders children with inline Tailwind styles', async () => {
+ const actualOutput = await render(
+
+
+ ,
+ );
- expect(actualOutput).not.toBeNull();
- });
+ expect(actualOutput).toMatchSnapshot();
});
test('
', async () => {
@@ -175,7 +165,7 @@ describe('Tailwind component', () => {
expect(actualOutput).toMatchSnapshot();
});
- test('it should not generate styles from text', async () => {
+ it("doesn't generate styles from text", async () => {
expect(
await render(container bg-red-500 bg-blue-300 ),
).toMatchSnapshot();
@@ -284,7 +274,7 @@ describe('Tailwind component', () => {
,
);
- expect(actualOutput).toContain('width:3rem');
+ expect(actualOutput).toMatchSnapshot();
});
it('preserves mso styles', async () => {
@@ -300,32 +290,33 @@ describe('Tailwind component', () => {
,
- );
+ ).then(pretty);
expect(actualOutput).toMatchSnapshot();
});
it('recognizes custom responsive screen', async () => {
- const config: TailwindConfig = {
- theme: {
- screens: {
- sm: { min: '640px' },
- md: { min: '768px' },
- lg: { min: '1024px' },
- xl: { min: '1280px' },
- '2xl': { min: '1536px' },
- },
- },
- };
const actualOutput = await render(
-
+
Test
Test
,
- );
+ ).then(pretty);
expect(actualOutput).toMatchSnapshot();
});
@@ -338,14 +329,13 @@ describe('Tailwind component', () => {
something tall
,
- );
+ ).then(pretty);
expect(actualOutput).toMatchSnapshot();
});
-});
-describe('non-inlinable styles', () => {
- /*
+ describe('with non-inlinable styles', () => {
+ /*
This test is because of https://github.com/resend/react-email/issues/1112
which was being caused because we required to, either have our component,
or a element directly inside the component for media queries to be applied
@@ -356,309 +346,277 @@ describe('non-inlinable styles', () => {
and apply the styles there. This also fixes the issue where it would not be allowed to use
Tailwind classes on the element as the would be required directly bellow Tailwind.
*/
- it('works with arbitrarily deep (in the React tree) elements', async () => {
- expect(
- await render(
-
-
-
-
-
-
-
- ,
- ),
- ).toMatchSnapshot();
+ it('works with arbitrarily deep (in the React tree) elements', async () => {
+ expect(
+ await render(
+
+
+
+
+
+
+
+ ,
+ ).then(pretty),
+ ).toMatchSnapshot();
+
+ const MyHead = (props: Record) => {
+ return ;
+ };
- const MyHead = (props: Record) => {
- return ;
- };
+ expect(
+ await render(
+
+
+
+
+
+
+
+ ,
+ ),
+ ).toMatchSnapshot();
+ });
- expect(
- await render(
-
-
-
+ it('adds css to and keep class names', async () => {
+ const actualOutput = await render(
+
+
+
-
+
-
- ,
- ),
- ).toMatchSnapshot();
- });
-
- it('does not have duplicate media queries', async () => {
- const Body = (props: { className: string; children: React.ReactNode }) => {
- return {props.children};
- };
- const output = await render(
-
-
-
-
-
- ,
- {
- pretty: true,
- },
- );
-
- expect(output).toMatchSnapshot();
- });
-
- it('adds css to
-
-
-
- ,
- );
-
- expect(actualOutput).toMatchSnapshot();
- });
+
+ ,
+ ).then(pretty);
- it('throws error when used without the head and with media query class names only very deeply nested', async () => {
- const Component1 = (props: Record) => {
- return (
-
- {props.children}
-
- );
- };
- const Component2 = (props: Record) => {
- return (
-
- {props.children}
-
- );
- };
- const Component3 = (props: Record) => {
- return (
-
- {props.children}
-
- );
- };
+ expect(actualOutput).toMatchSnapshot();
+ });
- function renderComplexEmailWithoutHead() {
- return render(
-
-
-
- Testing
-
+ it('throws error when used without the head and with media query class names very deeply nested', async () => {
+ const Component1 = (props: Record
) => {
+ return (
+
+ {props.children}
- ,
- );
- }
+ );
+ };
+ const Component2 = (props: Record) => {
+ return (
+
+ {props.children}
+
+ );
+ };
+ const Component3 = (props: Record) => {
+ return (
+
+ {props.children}
+
+ );
+ };
- await expect(
- renderComplexEmailWithoutHead,
- ).rejects.toThrowErrorMatchingSnapshot();
- });
+ function renderComplexEmailWithoutHead() {
+ return render(
+
+
+ ,
+ );
+ }
+
+ await expect(
+ renderComplexEmailWithoutHead,
+ ).rejects.toThrowErrorMatchingSnapshot();
+ });
- it('works with relatively complex media query utilities', async () => {
- const Email = () => {
- return (
-
-
- I am some text
-
- );
- };
+ it('works with relatively complex media query utilities', async () => {
+ const Email = () => {
+ return (
+
+
+ I am some text
+
+ );
+ };
- expect(await render( )).toMatchSnapshot();
- });
+ expect(await render( ).then(pretty)).toMatchSnapshot();
+ });
- it('throws an error when used without a ', async () => {
- function noHead() {
- return render(
-
-
- {/* */}
-
-
- ,
- );
- }
- await expect(noHead).rejects.toThrowErrorMatchingSnapshot();
- });
+ it('throws an error when used without a ', async () => {
+ function noHead() {
+ return render(
+
+
+ {/* */}
+
+
+ ,
+ ).then(pretty);
+ }
+ await expect(noHead).rejects.toThrowErrorMatchingSnapshot();
+ });
- it('persists existing elements', async () => {
- const actualOutput = await render(
-
-
-
-
-
-
-
-
-
-
- ,
- );
+ it('persists existing
elements', async () => {
+ const actualOutput = await render(
+
+
+
+
+
+
+
+
+
+
+ ,
+ ).then(pretty);
- expect(actualOutput).toMatchSnapshot();
+ expect(actualOutput).toMatchSnapshot();
+ });
});
-});
-describe('Custom theme config', () => {
- it('uses custom colors', async () => {
- const config: TailwindConfig = {
- theme: {
- extend: {
- colors: {
- custom: '#1fb6ff',
+ describe('with custom theme config', () => {
+ it('supports custom colors', async () => {
+ const config: TailwindConfig = {
+ theme: {
+ extend: {
+ colors: {
+ custom: '#1fb6ff',
+ },
},
},
- },
- };
+ };
- const actualOutput = await render(
-
-
- ,
- );
+ const actualOutput = await render(
+
+
+ ,
+ ).then(pretty);
- expect(actualOutput).toMatchSnapshot();
- });
+ expect(actualOutput).toMatchSnapshot();
+ });
- it('uses custom fonts', async () => {
- const config: TailwindConfig = {
- theme: {
- extend: {
- fontFamily: {
- sans: ['Graphik', 'sans-serif'],
- serif: ['Merriweather', 'serif'],
+ it('supports custom fonts', async () => {
+ const config: TailwindConfig = {
+ theme: {
+ extend: {
+ fontFamily: {
+ sans: ['Graphik', 'sans-serif'],
+ serif: ['Merriweather', 'serif'],
+ },
},
},
- },
- };
+ };
- const actualOutput = await render(
-
-
-
- ,
- );
+ const actualOutput = await render(
+
+
+
+ ,
+ ).then(pretty);
- expect(actualOutput).toMatchSnapshot();
- });
+ expect(actualOutput).toMatchSnapshot();
+ });
- it('uses custom spacing', async () => {
- const config: TailwindConfig = {
- theme: {
- extend: {
- spacing: {
- '8xl': '96rem',
+ it('supports custom spacing', async () => {
+ const config: TailwindConfig = {
+ theme: {
+ extend: {
+ spacing: {
+ '8xl': '96rem',
+ },
},
},
- },
- };
- const actualOutput = await render(
-
-
- ,
- );
- expect(actualOutput).toMatchSnapshot();
- });
+ };
+ const actualOutput = await render(
+
+
+ ,
+ ).then(pretty);
+ expect(actualOutput).toMatchSnapshot();
+ });
- it('uses custom border radius', async () => {
- const config: TailwindConfig = {
- theme: {
- extend: {
- borderRadius: {
- '4xl': '2rem',
+ it('supports custom border radius', async () => {
+ const config: TailwindConfig = {
+ theme: {
+ extend: {
+ borderRadius: {
+ '4xl': '2rem',
+ },
},
},
- },
- };
- const actualOutput = await render(
-
-
- ,
- );
- expect(actualOutput).toMatchSnapshot();
- });
+ };
+ const actualOutput = await render(
+
+
+ ,
+ ).then(pretty);
+ expect(actualOutput).toMatchSnapshot();
+ });
- it('uses custom text alignment', async () => {
- const config: TailwindConfig = {
- theme: {
- extend: {
- textAlign: {
- justify: 'justify',
+ it('supports custom text alignment', async () => {
+ const config: TailwindConfig = {
+ theme: {
+ extend: {
+ textAlign: {
+ justify: 'justify',
+ },
},
},
- },
- };
+ };
- const actualOutput = await render(
-
-
- ,
- );
+ const actualOutput = await render(
+
+
+ ,
+ ).then(pretty);
- expect(actualOutput).toMatchSnapshot();
+ expect(actualOutput).toMatchSnapshot();
+ });
});
-});
-describe('Custom plugins config', () => {
- it('uses custom plugins', async () => {
- const config: TailwindConfig = {
+ describe('with custom plugins config', () => {
+ const config = {
plugins: [
- ({ addUtilities }: any) => {
- const newUtilities = {
- '.border-custom': {
- border: '2px solid',
- },
- };
-
- addUtilities(newUtilities);
+ {
+ handler: (api) => {
+ api.addUtilities({
+ '.border-custom': {
+ border: '2px solid',
+ },
+ });
+ },
},
],
- };
+ } satisfies TailwindConfig;
- const actualOutput = await render(
-
-
- ,
- );
-
- expect(actualOutput).toMatchSnapshot();
- });
-
- it('uses custom plugins with responsive styles', async () => {
- const config: TailwindConfig = {
- plugins: [
- ({ addUtilities }: any) => {
- const newUtilities = {
- '.border-custom': {
- border: '2px solid',
- },
- };
+ it('supports custom plugins', async () => {
+ const actualOutput = await render(
+
+
+ ,
+ ).then(pretty);
- addUtilities(newUtilities);
- },
- ],
- };
+ expect(actualOutput).toMatchSnapshot();
+ });
- const actualOutput = await render(
-
-
-
-
-
-
-
- ,
- );
+ it('supports custom plugins with responsive styles', async () => {
+ const actualOutput = await render(
+
+
+
+
+
+
+
+ ,
+ ).then(pretty);
- expect(actualOutput).toMatchSnapshot();
+ expect(actualOutput).toMatchSnapshot();
+ });
});
});
diff --git a/packages/tailwind/src/tailwind.tsx b/packages/tailwind/src/tailwind.tsx
index f8b100bfb2..45f4751ffc 100644
--- a/packages/tailwind/src/tailwind.tsx
+++ b/packages/tailwind/src/tailwind.tsx
@@ -1,27 +1,18 @@
-import { Root } from 'postcss';
+import { type CssNode, generate, List, type StyleSheet } from 'css-tree';
import * as React from 'react';
-import type { Config as TailwindOriginalConfig } from 'tailwindcss';
-import { minifyCss } from './utils/css/minify-css';
-import { removeRuleDuplicatesFromRoot } from './utils/css/remove-rule-duplicates-from-root';
+import type { Config } from 'tailwindcss';
+import { useSuspensedPromise } from './hooks/use-suspended-promise';
+import { extractRulesPerClass } from './utils/css/extract-rules-per-class';
+import { getCustomProperties } from './utils/css/get-custom-properties';
+import { resolveAllCssVariables } from './utils/css/resolve-all-css-variables';
+import { resolveCalcExpressions } from './utils/css/resolve-calc-expressions';
+import { sanitizeDeclarations } from './utils/css/sanitize-declarations';
+import { sanitizeNonInlinableRules } from './utils/css/sanitize-non-inlinable-rules';
import { mapReactTree } from './utils/react/map-react-tree';
import { cloneElementWithInlinedStyles } from './utils/tailwindcss/clone-element-with-inlined-styles';
import { setupTailwind } from './utils/tailwindcss/setup-tailwind';
-export type TailwindConfig = Pick<
- TailwindOriginalConfig,
- | 'important'
- | 'prefix'
- | 'separator'
- | 'safelist'
- | 'blocklist'
- | 'presets'
- | 'future'
- | 'experimental'
- | 'darkMode'
- | 'theme'
- | 'corePlugins'
- | 'plugins'
->;
+export type TailwindConfig = Omit;
export interface TailwindProps {
children: React.ReactNode;
@@ -36,128 +27,138 @@ export interface EmailElementProps {
export const pixelBasedPreset: TailwindConfig = {
theme: {
- fontSize: {
- xs: ['12px', { lineHeight: '16px' }],
- sm: ['14px', { lineHeight: '20px' }],
- base: ['16px', { lineHeight: '24px' }],
- lg: ['18px', { lineHeight: '28px' }],
- xl: ['20px', { lineHeight: '28px' }],
- '2xl': ['24px', { lineHeight: '32px' }],
- '3xl': ['30px', { lineHeight: '36px' }],
- '4xl': ['36px', { lineHeight: '36px' }],
- '5xl': ['48px', { lineHeight: '1' }],
- '6xl': ['60px', { lineHeight: '1' }],
- '7xl': ['72px', { lineHeight: '1' }],
- '8xl': ['96px', { lineHeight: '1' }],
- '9xl': ['144px', { lineHeight: '1' }],
- },
- spacing: {
- px: '1px',
- 0: '0',
- 0.5: '2px',
- 1: '4px',
- 1.5: '6px',
- 2: '8px',
- 2.5: '10px',
- 3: '12px',
- 3.5: '14px',
- 4: '16px',
- 5: '20px',
- 6: '24px',
- 7: '28px',
- 8: '32px',
- 9: '36px',
- 10: '40px',
- 11: '44px',
- 12: '48px',
- 14: '56px',
- 16: '64px',
- 20: '80px',
- 24: '96px',
- 28: '112px',
- 32: '128px',
- 36: '144px',
- 40: '160px',
- 44: '176px',
- 48: '192px',
- 52: '208px',
- 56: '224px',
- 60: '240px',
- 64: '256px',
- 72: '288px',
- 80: '320px',
- 96: '384px',
+ extend: {
+ fontSize: {
+ xs: ['12px', { lineHeight: '16px' }],
+ sm: ['14px', { lineHeight: '20px' }],
+ base: ['16px', { lineHeight: '24px' }],
+ lg: ['18px', { lineHeight: '28px' }],
+ xl: ['20px', { lineHeight: '28px' }],
+ '2xl': ['24px', { lineHeight: '32px' }],
+ '3xl': ['30px', { lineHeight: '36px' }],
+ '4xl': ['36px', { lineHeight: '36px' }],
+ '5xl': ['48px', { lineHeight: '1' }],
+ '6xl': ['60px', { lineHeight: '1' }],
+ '7xl': ['72px', { lineHeight: '1' }],
+ '8xl': ['96px', { lineHeight: '1' }],
+ '9xl': ['144px', { lineHeight: '1' }],
+ },
+ spacing: {
+ px: '1px',
+ 0: '0',
+ 0.5: '2px',
+ 1: '4px',
+ 1.5: '6px',
+ 2: '8px',
+ 2.5: '10px',
+ 3: '12px',
+ 3.5: '14px',
+ 4: '16px',
+ 5: '20px',
+ 6: '24px',
+ 7: '28px',
+ 8: '32px',
+ 9: '36px',
+ 10: '40px',
+ 11: '44px',
+ 12: '48px',
+ 14: '56px',
+ 16: '64px',
+ 20: '80px',
+ 24: '96px',
+ 28: '112px',
+ 32: '128px',
+ 36: '144px',
+ 40: '160px',
+ 44: '176px',
+ 48: '192px',
+ 52: '208px',
+ 56: '224px',
+ 60: '240px',
+ 64: '256px',
+ 72: '288px',
+ 80: '320px',
+ 96: '384px',
+ },
},
},
};
-export const Tailwind: React.FC = ({ children, config }) => {
- const tailwind = setupTailwind(config ?? {});
-
- const nonInlineStylesRootToApply = new Root();
- let mediaQueryClassesForAllElement: string[] = [];
-
- let hasNonInlineStylesToApply = false as boolean;
+export function Tailwind({ children, config }: TailwindProps) {
+ const tailwindSetup = useSuspensedPromise(
+ () => setupTailwind(config ?? {}),
+ JSON.stringify(config, (_key, value) =>
+ typeof value === 'function' ? value.toString() : value,
+ ),
+ );
+ let classesUsed: string[] = [];
let mappedChildren: React.ReactNode = mapReactTree(children, (node) => {
if (React.isValidElement(node)) {
- const {
- elementWithInlinedStyles,
- nonInlinableClasses,
- nonInlineStyleNodes,
- } = cloneElementWithInlinedStyles(node, tailwind);
- mediaQueryClassesForAllElement =
- mediaQueryClassesForAllElement.concat(nonInlinableClasses);
- nonInlineStylesRootToApply.append(nonInlineStyleNodes);
-
- if (nonInlinableClasses.length > 0 && !hasNonInlineStylesToApply) {
- hasNonInlineStylesToApply = true;
+ if (node.props.className) {
+ const classes = node.props.className?.split(/\s+/);
+ classesUsed = [...classesUsed, ...classes];
+ tailwindSetup.addUtilities(classes);
}
-
- return elementWithInlinedStyles;
}
return node;
});
- removeRuleDuplicatesFromRoot(nonInlineStylesRootToApply);
+ const styleSheet = tailwindSetup.getStyleSheet();
+ resolveAllCssVariables(styleSheet);
+ resolveCalcExpressions(styleSheet);
+ sanitizeDeclarations(styleSheet);
- if (hasNonInlineStylesToApply) {
- let hasAppliedNonInlineStyles = false as boolean;
+ const { inlinable: inlinableRules, nonInlinable: nonInlinableRules } =
+ extractRulesPerClass(styleSheet, classesUsed);
+ sanitizeNonInlinableRules(styleSheet);
- mappedChildren = mapReactTree(mappedChildren, (node) => {
- if (hasAppliedNonInlineStyles) {
- return node;
- }
+ const customProperties = getCustomProperties(styleSheet);
+
+ const nonInlineStyles: StyleSheet = {
+ type: 'StyleSheet',
+ children: new List().fromArray(
+ Array.from(nonInlinableRules.values()),
+ ),
+ };
+
+ const hasNonInlineStylesToApply = nonInlinableRules.size > 0;
+ let appliedNonInlineStyles = false as boolean;
+
+ mappedChildren = mapReactTree(mappedChildren, (node) => {
+ if (React.isValidElement(node)) {
+ const elementWithInlinedStyles = cloneElementWithInlinedStyles(
+ node,
+ inlinableRules,
+ nonInlinableRules,
+ customProperties,
+ );
- if (React.isValidElement(node)) {
- if (node.type === 'head') {
- hasAppliedNonInlineStyles = true;
-
- /* only minify here since it is the only place that is going to be in the DOM */
- const styleElement = (
-
- );
-
- return React.cloneElement(
- node,
- node.props,
- node.props.children,
- styleElement,
- );
- }
+ if (elementWithInlinedStyles.type === 'head') {
+ appliedNonInlineStyles = true;
+
+ const styleElement = ;
+
+ return React.cloneElement(
+ elementWithInlinedStyles,
+ elementWithInlinedStyles.props,
+ styleElement,
+ elementWithInlinedStyles.props.children,
+ );
}
- return node;
- });
+ return elementWithInlinedStyles;
+ }
- if (!hasAppliedNonInlineStyles) {
- throw new Error(
- `You are trying to use the following Tailwind classes that cannot be inlined: ${mediaQueryClassesForAllElement.join(
- ' ',
- )}.
+ return node;
+ });
+
+ if (hasNonInlineStylesToApply && !appliedNonInlineStyles) {
+ throw new Error(
+ `You are trying to use the following Tailwind classes that cannot be inlined: ${Array.from(
+ nonInlinableRules.keys(),
+ ).join(' ')}.
For the media queries to work properly on rendering, they need to be added into a