react-email 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # react-email
2
2
 
3
+ ## 6.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3875d2a: add a `--clients` option to `email dev` and a `COMPATIBILITY_EMAIL_CLIENTS` environment variable to narrow which email clients trigger compatibility warnings. By default the preview still warns for `gmail`, `apple-mail`, `outlook`, and `yahoo`. Teams that only target one or two clients can now skip the noise: `email dev --clients outlook,apple-mail`. The CLI flag wins over the env var; an empty or fully-invalid list falls back to the defaults so warnings can't be silently switched off. Builds on #2797 by @ReemX.
8
+
9
+ ### Patch Changes
10
+
11
+ - d47825a: Add accessibility defaults to components: `dir`/`lang` on `Body`, an empty `alt` fallback on `Img`, `role="presentation"` on the `Markdown` table, and a `<title>` from `Preview`.
12
+
3
13
  ## 6.4.0
4
14
 
5
15
  ### Minor Changes
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { spawn } from "node:child_process";
4
- import { Option, program } from "commander";
4
+ import { InvalidArgumentError, Option, program } from "commander";
5
5
  import * as fs$1 from "node:fs";
6
6
  import fs, { existsSync, promises, statSync } from "node:fs";
7
7
  import * as path$2 from "node:path";
@@ -6523,7 +6523,7 @@ const getEmailsDirectoryMetadata = async (absolutePathToEmailsDirectory, keepFil
6523
6523
  //#region package.json
6524
6524
  var package_default = {
6525
6525
  name: "react-email",
6526
- version: "6.4.0",
6526
+ version: "6.5.0",
6527
6527
  description: "A live preview of your emails right in your browser.",
6528
6528
  bin: { "email": "./dist/cli/index.mjs" },
6529
6529
  type: "module",
@@ -7226,13 +7226,14 @@ const conf = new Conf({
7226
7226
  const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
7227
7227
  //#endregion
7228
7228
  //#region src/cli/utils/preview/get-env-variables-for-preview-app.ts
7229
- const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey) => {
7229
+ const getEnvVariablesForPreviewApp = (relativePathToEmailsDirectory, previewServerLocation, cwd, resendApiKey, compatibilityClients) => {
7230
7230
  return {
7231
7231
  REACT_EMAIL_INTERNAL_EMAILS_DIR_RELATIVE_PATH: relativePathToEmailsDirectory,
7232
7232
  REACT_EMAIL_INTERNAL_EMAILS_DIR_ABSOLUTE_PATH: path.resolve(cwd, relativePathToEmailsDirectory),
7233
7233
  REACT_EMAIL_INTERNAL_PREVIEW_SERVER_LOCATION: previewServerLocation,
7234
7234
  REACT_EMAIL_INTERNAL_USER_PROJECT_LOCATION: cwd,
7235
- REACT_EMAIL_INTERNAL_RESEND_API_KEY: resendApiKey
7235
+ REACT_EMAIL_INTERNAL_RESEND_API_KEY: resendApiKey,
7236
+ ...compatibilityClients !== void 0 && { COMPATIBILITY_EMAIL_CLIENTS: compatibilityClients }
7236
7237
  };
7237
7238
  };
7238
7239
  //#endregion
@@ -7278,7 +7279,7 @@ const safeAsyncServerListen = (server, port) => {
7278
7279
  });
7279
7280
  });
7280
7281
  };
7281
- const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port) => {
7282
+ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port, compatibilityClients) => {
7282
7283
  const [majorNodeVersion] = process.versions.node.split(".");
7283
7284
  if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
7284
7285
  console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`);
@@ -7312,7 +7313,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
7312
7313
  } else {
7313
7314
  const nextPortToTry = port + 1;
7314
7315
  console.warn(` ${logSymbols.warning} Port ${port} is already in use, trying ${nextPortToTry}`);
7315
- return startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry);
7316
+ return startDevServer(emailsDirRelativePath, staticBaseDirRelativePath, nextPortToTry, compatibilityClients);
7316
7317
  }
7317
7318
  devServer.on("close", async () => {
7318
7319
  await app.close();
@@ -7334,7 +7335,7 @@ const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath,
7334
7335
  process.env = {
7335
7336
  NODE_ENV: "development",
7336
7337
  ...process.env,
7337
- ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"))
7338
+ ...getEnvVariablesForPreviewApp(path.normalize(emailsDirRelativePath), previewServerLocation, process.cwd(), conf.get("resendApiKey"), compatibilityClients)
7338
7339
  };
7339
7340
  if (!process.env.ESBUILD_BINARY_PATH) try {
7340
7341
  const esbuild = createJiti(previewServer.esmResolve("esbuild"));
@@ -7433,13 +7434,13 @@ const tree = async (dirPath, depth) => {
7433
7434
  };
7434
7435
  //#endregion
7435
7436
  //#region src/cli/commands/dev.ts
7436
- const dev = async ({ dir: emailsDirRelativePath, port }) => {
7437
+ const dev = async ({ dir: emailsDirRelativePath, port, clients }) => {
7437
7438
  try {
7438
7439
  if (!fs.existsSync(emailsDirRelativePath)) {
7439
7440
  console.error(`Missing ${emailsDirRelativePath} folder`);
7440
7441
  process.exit(1);
7441
7442
  }
7442
- await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10)), emailsDirRelativePath);
7443
+ await setupHotreloading(await startDevServer(emailsDirRelativePath, emailsDirRelativePath, Number.parseInt(port, 10), clients), emailsDirRelativePath);
7443
7444
  } catch (error) {
7444
7445
  console.log(error);
7445
7446
  process.exit(1);
@@ -7727,7 +7728,41 @@ const start = async () => {
7727
7728
  }
7728
7729
  };
7729
7730
  //#endregion
7731
+ //#region src/cli/utils/email-clients.ts
7732
+ const ALL_EMAIL_CLIENTS = [
7733
+ "gmail",
7734
+ "outlook",
7735
+ "yahoo",
7736
+ "apple-mail",
7737
+ "aol",
7738
+ "thunderbird",
7739
+ "microsoft",
7740
+ "samsung-email",
7741
+ "sfr",
7742
+ "orange",
7743
+ "protonmail",
7744
+ "hey",
7745
+ "mail-ru",
7746
+ "fastmail",
7747
+ "laposte",
7748
+ "t-online-de",
7749
+ "free-fr",
7750
+ "gmx",
7751
+ "web-de",
7752
+ "ionos-1and1",
7753
+ "rainloop",
7754
+ "wp-pl"
7755
+ ];
7756
+ //#endregion
7730
7757
  //#region src/cli/index.ts
7758
+ const parseClientsOption = (value) => {
7759
+ const requested = value.split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
7760
+ if (requested.length === 0) throw new InvalidArgumentError("--clients requires at least one email client.");
7761
+ const known = new Set(ALL_EMAIL_CLIENTS);
7762
+ const invalid = requested.filter((entry) => !known.has(entry));
7763
+ if (invalid.length > 0) throw new InvalidArgumentError(`Unknown email client(s): ${invalid.join(", ")}. Supported: ${ALL_EMAIL_CLIENTS.join(", ")}.`);
7764
+ return requested.join(",");
7765
+ };
7731
7766
  const requiredFlags = ["--experimental-vm-modules", "--disable-warning=ExperimentalWarning"];
7732
7767
  if (!requiredFlags.every((flag) => process.execArgv.includes(flag))) spawn(process.execPath, [
7733
7768
  ...requiredFlags,
@@ -7739,7 +7774,7 @@ if (!requiredFlags.every((flag) => process.execArgv.includes(flag))) spawn(proce
7739
7774
  });
7740
7775
  else {
7741
7776
  program.name("react-email").description("A live preview of your emails right in your browser").version(package_default.version);
7742
- program.command("dev").description("Starts the preview email development app").option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-p --port <port>", "Port to run dev server on", "3000").action(dev);
7777
+ program.command("dev").description("Starts the preview email development app").option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-p --port <port>", "Port to run dev server on", "3000").option("-c, --clients <clients>", "Comma-separated list of email clients to show compatibility warnings for (overrides COMPATIBILITY_EMAIL_CLIENTS)", parseClientsOption).action(dev);
7743
7778
  program.command("build").description("Copies the preview app for onto .react-email and builds it").option("-d, --dir <path>", "Directory with your email templates", "./emails").addOption(new Option("-p, --packageManager <name>").hideHelp()).action(build$1);
7744
7779
  program.command("start").description("Runs the built preview app that is inside of \".react-email\"").action(start);
7745
7780
  program.command("export").description("Build the templates to the `out` directory").option("--outDir <path>", "Output directory", "out").option("-p, --pretty", "Pretty print the output", false).option("-t, --plainText", "Set output format as plain text", false).option("-d, --dir <path>", "Directory with your email templates", "./emails").option("-s, --silent", "To, or not to show a spinner with process information", false).action(({ outDir, pretty, plainText, silent, dir: srcDir }) => exportTemplates(outDir, srcDir, {
package/dist/index.cjs CHANGED
@@ -76,6 +76,8 @@ const Body = react.forwardRef(({ children, style, ...props }, ref) => {
76
76
  if (style) for (const property of [...marginProperties, ...paddingProperties]) bodyStyle[property] = style[property] !== void 0 ? 0 : void 0;
77
77
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("body", {
78
78
  ...props,
79
+ dir: props.dir ?? "ltr",
80
+ lang: props.lang ?? "en",
79
81
  style: bodyStyle,
80
82
  ref,
81
83
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {
@@ -86,6 +88,8 @@ const Body = react.forwardRef(({ children, style, ...props }, ref) => {
86
88
  role: "presentation",
87
89
  align: "center",
88
90
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
91
+ dir: props.dir ?? "ltr",
92
+ lang: props.lang ?? "en",
89
93
  style,
90
94
  children
91
95
  }) }) })
@@ -17601,7 +17605,7 @@ Html.displayName = "Html";
17601
17605
  //#region src/components/img/img.tsx
17602
17606
  const Img = react.forwardRef(({ alt, src, width, height, style, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
17603
17607
  ...props,
17604
- alt,
17608
+ alt: alt ?? "",
17605
17609
  height,
17606
17610
  ref,
17607
17611
  src,
@@ -17850,7 +17854,7 @@ const Markdown = react.forwardRef(({ children, markdownContainerStyles, markdown
17850
17854
  const tbodyRows = rows.map((row) => renderer.tablerow({ text: row.map((cell) => renderer.tablecell(cell)).join("") })).join("");
17851
17855
  const thead = `<thead${styleThead ? ` style="${styleThead}"` : ""}>\n${theadRow}</thead>`;
17852
17856
  const tbody = `<tbody${styleTbody ? ` style="${styleTbody}"` : ""}>${tbodyRows}</tbody>`;
17853
- return `<table${styleTable ? ` style="${styleTable}"` : ""}>\n${thead}\n${tbody}</table>\n`;
17857
+ return `<table role="presentation"${styleTable ? ` style="${styleTable}"` : ""}>\n${thead}\n${tbody}</table>\n`;
17854
17858
  };
17855
17859
  renderer.tablecell = ({ tokens, align, header }) => {
17856
17860
  const text = renderer.parser.parseInline(tokens);
@@ -17877,7 +17881,7 @@ Markdown.displayName = "Markdown";
17877
17881
  const PREVIEW_MAX_LENGTH = 200;
17878
17882
  const Preview = react.forwardRef(({ children = "", ...props }, ref) => {
17879
17883
  const text = (Array.isArray(children) ? children.join("") : children).substring(0, PREVIEW_MAX_LENGTH);
17880
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17884
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: text }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17881
17885
  style: {
17882
17886
  display: "none",
17883
17887
  overflow: "hidden",
@@ -17890,7 +17894,7 @@ const Preview = react.forwardRef(({ children = "", ...props }, ref) => {
17890
17894
  ...props,
17891
17895
  ref,
17892
17896
  children: [text, renderWhiteSpace(text)]
17893
- });
17897
+ })] });
17894
17898
  });
17895
17899
  Preview.displayName = "Preview";
17896
17900
  const whiteSpaceCodes = "\xA0‌​‍‎‏";
package/dist/index.mjs CHANGED
@@ -55,6 +55,8 @@ const Body = React$1.forwardRef(({ children, style, ...props }, ref) => {
55
55
  if (style) for (const property of [...marginProperties, ...paddingProperties]) bodyStyle[property] = style[property] !== void 0 ? 0 : void 0;
56
56
  return /* @__PURE__ */ jsx("body", {
57
57
  ...props,
58
+ dir: props.dir ?? "ltr",
59
+ lang: props.lang ?? "en",
58
60
  style: bodyStyle,
59
61
  ref,
60
62
  children: /* @__PURE__ */ jsx("table", {
@@ -65,6 +67,8 @@ const Body = React$1.forwardRef(({ children, style, ...props }, ref) => {
65
67
  role: "presentation",
66
68
  align: "center",
67
69
  children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
70
+ dir: props.dir ?? "ltr",
71
+ lang: props.lang ?? "en",
68
72
  style,
69
73
  children
70
74
  }) }) })
@@ -17580,7 +17584,7 @@ Html.displayName = "Html";
17580
17584
  //#region src/components/img/img.tsx
17581
17585
  const Img = React$1.forwardRef(({ alt, src, width, height, style, ...props }, ref) => /* @__PURE__ */ jsx("img", {
17582
17586
  ...props,
17583
- alt,
17587
+ alt: alt ?? "",
17584
17588
  height,
17585
17589
  ref,
17586
17590
  src,
@@ -17829,7 +17833,7 @@ const Markdown = React$1.forwardRef(({ children, markdownContainerStyles, markdo
17829
17833
  const tbodyRows = rows.map((row) => renderer.tablerow({ text: row.map((cell) => renderer.tablecell(cell)).join("") })).join("");
17830
17834
  const thead = `<thead${styleThead ? ` style="${styleThead}"` : ""}>\n${theadRow}</thead>`;
17831
17835
  const tbody = `<tbody${styleTbody ? ` style="${styleTbody}"` : ""}>${tbodyRows}</tbody>`;
17832
- return `<table${styleTable ? ` style="${styleTable}"` : ""}>\n${thead}\n${tbody}</table>\n`;
17836
+ return `<table role="presentation"${styleTable ? ` style="${styleTable}"` : ""}>\n${thead}\n${tbody}</table>\n`;
17833
17837
  };
17834
17838
  renderer.tablecell = ({ tokens, align, header }) => {
17835
17839
  const text = renderer.parser.parseInline(tokens);
@@ -17856,7 +17860,7 @@ Markdown.displayName = "Markdown";
17856
17860
  const PREVIEW_MAX_LENGTH = 200;
17857
17861
  const Preview = React$1.forwardRef(({ children = "", ...props }, ref) => {
17858
17862
  const text = (Array.isArray(children) ? children.join("") : children).substring(0, PREVIEW_MAX_LENGTH);
17859
- return /* @__PURE__ */ jsxs("div", {
17863
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("title", { children: text }), /* @__PURE__ */ jsxs("div", {
17860
17864
  style: {
17861
17865
  display: "none",
17862
17866
  overflow: "hidden",
@@ -17869,7 +17873,7 @@ const Preview = React$1.forwardRef(({ children = "", ...props }, ref) => {
17869
17873
  ...props,
17870
17874
  ref,
17871
17875
  children: [text, renderWhiteSpace(text)]
17872
- });
17876
+ })] });
17873
17877
  });
17874
17878
  Preview.displayName = "Preview";
17875
17879
  const whiteSpaceCodes = "\xA0‌​‍‎‏";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "6.4.0",
3
+ "version": "6.5.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.mjs"
@@ -1,23 +1,23 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`<Body> component > margin resetting behavior > should reset the margin property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
3
+ exports[`<Body> component > margin resetting behavior > should reset the margin property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
4
4
 
5
- exports[`<Body> component > margin resetting behavior > should reset the marginBlock property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-block:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-block:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
5
+ exports[`<Body> component > margin resetting behavior > should reset the marginBlock property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-block:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-block:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
6
6
 
7
- exports[`<Body> component > margin resetting behavior > should reset the marginBlockEnd property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-block-end:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-block-end:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
7
+ exports[`<Body> component > margin resetting behavior > should reset the marginBlockEnd property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-block-end:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-block-end:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
8
8
 
9
- exports[`<Body> component > margin resetting behavior > should reset the marginBlockStart property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-block-start:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-block-start:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
9
+ exports[`<Body> component > margin resetting behavior > should reset the marginBlockStart property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-block-start:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-block-start:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
10
10
 
11
- exports[`<Body> component > margin resetting behavior > should reset the marginBottom property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-bottom:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-bottom:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
11
+ exports[`<Body> component > margin resetting behavior > should reset the marginBottom property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-bottom:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-bottom:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
12
12
 
13
- exports[`<Body> component > margin resetting behavior > should reset the marginInline property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-inline:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-inline:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
13
+ exports[`<Body> component > margin resetting behavior > should reset the marginInline property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-inline:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-inline:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
14
14
 
15
- exports[`<Body> component > margin resetting behavior > should reset the marginInlineEnd property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-inline-end:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-inline-end:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
15
+ exports[`<Body> component > margin resetting behavior > should reset the marginInlineEnd property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-inline-end:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-inline-end:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
16
16
 
17
- exports[`<Body> component > margin resetting behavior > should reset the marginInlineStart property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-inline-start:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-inline-start:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
17
+ exports[`<Body> component > margin resetting behavior > should reset the marginInlineStart property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-inline-start:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-inline-start:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
18
18
 
19
- exports[`<Body> component > margin resetting behavior > should reset the marginLeft property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-left:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-left:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
19
+ exports[`<Body> component > margin resetting behavior > should reset the marginLeft property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-left:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-left:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
20
20
 
21
- exports[`<Body> component > margin resetting behavior > should reset the marginRight property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-right:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-right:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
21
+ exports[`<Body> component > margin resetting behavior > should reset the marginRight property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-right:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-right:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
22
22
 
23
- exports[`<Body> component > margin resetting behavior > should reset the marginTop property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body style="margin-top:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="margin-top:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
23
+ exports[`<Body> component > margin resetting behavior > should reset the marginTop property when it comes from props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en" style="margin-top:0"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="margin-top:10px">Random text</td></tr></tbody></table><!--/$--></body>"`;
@@ -23,7 +23,7 @@ describe('<Body> component', () => {
23
23
  it('renders correctly', async () => {
24
24
  const actualOutput = await render(<Body>Lorem ipsum</Body>);
25
25
  expect(actualOutput).toMatchInlineSnapshot(
26
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td>Lorem ipsum</td></tr></tbody></table><!--/$--></body>"`,
26
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><body dir="ltr" lang="en"><!--$--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en">Lorem ipsum</td></tr></tbody></table><!--/$--></body>"`,
27
27
  );
28
28
  });
29
29
 
@@ -19,7 +19,13 @@ export const Body = React.forwardRef<HTMLBodyElement, BodyProps>(
19
19
  }
20
20
  }
21
21
  return (
22
- <body {...props} style={bodyStyle} ref={ref}>
22
+ <body
23
+ {...props}
24
+ dir={props.dir ?? 'ltr'}
25
+ lang={props.lang ?? 'en'}
26
+ style={bodyStyle}
27
+ ref={ref}
28
+ >
23
29
  <table
24
30
  border={0}
25
31
  width="100%"
@@ -36,7 +42,13 @@ export const Body = React.forwardRef<HTMLBodyElement, BodyProps>(
36
42
 
37
43
  See https://github.com/resend/react-email/issues/662.
38
44
  */}
39
- <td style={style}>{children}</td>
45
+ <td
46
+ dir={props.dir ?? 'ltr'}
47
+ lang={props.lang ?? 'en'}
48
+ style={style}
49
+ >
50
+ {children}
51
+ </td>
40
52
  </tr>
41
53
  </tbody>
42
54
  </table>
@@ -6,7 +6,7 @@ export const Img = React.forwardRef<HTMLImageElement, ImgProps>(
6
6
  ({ alt, src, width, height, style, ...props }, ref) => (
7
7
  <img
8
8
  {...props}
9
- alt={alt}
9
+ alt={alt ?? ''}
10
10
  height={height}
11
11
  ref={ref}
12
12
  src={src}
@@ -191,7 +191,7 @@ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(
191
191
  const thead = `<thead${styleThead ? ` style="${styleThead}"` : ''}>\n${theadRow}</thead>`;
192
192
  const tbody = `<tbody${styleTbody ? ` style="${styleTbody}"` : ''}>${tbodyRows}</tbody>`;
193
193
 
194
- return `<table${styleTable ? ` style="${styleTable}"` : ''}>\n${thead}\n${tbody}</table>\n`;
194
+ return `<table role="presentation"${styleTable ? ` style="${styleTable}"` : ''}>\n${thead}\n${tbody}</table>\n`;
195
195
  };
196
196
 
197
197
  renderer.tablecell = ({ tokens, align, header }) => {
@@ -5,7 +5,7 @@ describe('<Preview> component', () => {
5
5
  it('renders correctly', async () => {
6
6
  const actualOutput = await render(<Preview>Email preview text</Preview>);
7
7
  expect(actualOutput).toMatchInlineSnapshot(
8
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
8
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
9
9
  );
10
10
  });
11
11
 
@@ -14,7 +14,7 @@ describe('<Preview> component', () => {
14
14
  <Preview>Email preview text</Preview>,
15
15
  );
16
16
  expect(actualOutputArray).toMatchInlineSnapshot(
17
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
17
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>Email preview text</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">Email preview text<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><!--/$-->"`,
18
18
  );
19
19
  });
20
20
 
@@ -22,7 +22,7 @@ describe('<Preview> component', () => {
22
22
  const longText = 'really long'.repeat(100);
23
23
  const actualOutputLong = await render(<Preview>{longText}</Preview>);
24
24
  expect(actualOutputLong).toMatchInlineSnapshot(
25
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">really longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longre</div><!--/$-->"`,
25
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><title>really longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longre</title><!--$--><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0" data-skip-in-text="true">really longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longre</div><!--/$-->"`,
26
26
  );
27
27
  });
28
28
  });
@@ -15,22 +15,25 @@ export const Preview = React.forwardRef<HTMLDivElement, PreviewProps>(
15
15
  ).substring(0, PREVIEW_MAX_LENGTH);
16
16
 
17
17
  return (
18
- <div
19
- style={{
20
- display: 'none',
21
- overflow: 'hidden',
22
- lineHeight: '1px',
23
- opacity: 0,
24
- maxHeight: 0,
25
- maxWidth: 0,
26
- }}
27
- data-skip-in-text={true}
28
- {...props}
29
- ref={ref}
30
- >
31
- {text}
32
- {renderWhiteSpace(text)}
33
- </div>
18
+ <>
19
+ <title>{text}</title>
20
+ <div
21
+ style={{
22
+ display: 'none',
23
+ overflow: 'hidden',
24
+ lineHeight: '1px',
25
+ opacity: 0,
26
+ maxHeight: 0,
27
+ maxWidth: 0,
28
+ }}
29
+ data-skip-in-text={true}
30
+ {...props}
31
+ ref={ref}
32
+ >
33
+ {text}
34
+ {renderWhiteSpace(text)}
35
+ </div>
36
+ </>
34
37
  );
35
38
  },
36
39
  );
@@ -394,7 +394,7 @@ describe('Tailwind component', () => {
394
394
  );
395
395
 
396
396
  expect(actualOutput).toMatchInlineSnapshot(
397
- `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><style>@media (prefers-color-scheme:dark){.text-body{color:orange!important}}</style></head><body class="text-body"><!--$--><!--html--><!--head--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td style="color:green">this is the body</td></tr></tbody></table><!--/$--></body></html>"`,
397
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><style>@media (prefers-color-scheme:dark){.text-body{color:orange!important}}</style></head><body class="text-body" dir="ltr" lang="en"><!--$--><!--html--><!--head--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="color:green">this is the body</td></tr></tbody></table><!--/$--></body></html>"`,
398
398
  );
399
399
  });
400
400