Skip to content

Releases: unlayer/elements

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 13 Oct 12:24
97d0541

What's Changed

  • fix: render without random numbers, so Next.js 16 can prerender emails by @ivoIturrieta in #65

Full Changelog: v0.2.0...v0.2.1

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 07:50
3ac5f73

Elements 0.2.0 renders templates with React itself: hooks, Context, Fragments, memo/forwardRef and conditions now behave as React runs them, and the HTML, the head and the design JSON are all made from the same render. It also ships the security fixes and phone settings below, and is the version @unlayer/migrate builds on, the new one-command migration from React Email.

One React render now runs the template, and the HTML, the head's CSS and the design JSON are all made from it. 0.1.22 walked the tree three separate ways, which disagreed. Rows from Fragments and components came out in a web layout in emails, ids repeated, a Context Provider never reached the design, and a component with hooks could blank its Column. Now hooks, Context, useId, Fragments, memo/forwardRef and conditions behave as React runs them. A component that throws, suspends or is async is left out with a console message naming it, and the rest of the email renders.

Also fixed:

  • <Row noStackMobile> works.
  • rgb() colors render (Tailwind writes them).
  • Phone settings match the editor's device CSS.
  • Roots accept fonts, textDirection and lang.
  • renderToHtml(<Welcome />) renders a template component as its root.

Security fixes: text placed straight in a Row or Column, Heading and Button children, Menu and Social text, link URLs, previewText, phone settings written into the head's CSS, and textDirection could all inject markup. They're escaped now.

Elements renders with react-dom/server, as 0.1.22 did, and does nothing at import. Called inside an app's own react-dom/server render (a preview page that works out its design in useMemo), it renders with a second react-dom module where one shares the app's React, so the page's hooks keep working. Otherwise it renders the way 0.1.22 did.

It's tested in:

  • Node 18–25 (ESM and CJS);
  • React 18 and 19;
  • Next.js 14 and 15 (route handlers, Server Components, Client Components, an edge route);
  • Jest 30 with jsdom;
  • browser and server bundles.

The bundle is 117,919 bytes (budget 118KB; 0.1.22 was about 76KB).

Behavior changes

  • <Row noStackMobile> keeps columns side by side on phones; the design saves it as _override.mobile.noStackMobile.
  • Text shows as typed:
    • Heading and Button children, previewText, and text placed straight in a Row or Column escape markup and entities, as React shows text.
    • HTML goes in the text prop, Paragraph's html or an <Html> block.
    • So does template syntax with < or >, such as {% if a > b %}.
    • Write the inbox padding characters themselves, not &nbsp;&zwnj;.
  • Menu text in emails escapes markup and double quotes, also inside a template tag (the text is in an aria-label too), but keeps entities and single quotes, as in 0.1.22: write a template's string in single quotes ({{ name | default: 'Shop' }}).
  • ", < and > in link URLs are percent-encoded. SendGrid's <%name%> tags stay as written.
  • Every document's head carries the editor's device CSS (hideOnDesktop/hideOnMobile), so menu separators hide on phones.
  • Rows from Fragments and components are email tables with their own ids, so emails built that way are larger (a review's example went from 37KB to 81KB, closer to Gmail's 102KB clip).
  • renderToJson keeps phone settings, visibility flags and a Provider's context.
  • useId() values differ from 0.1.22's, but are unique and the same in the HTML and the design.
  • A component that throws, suspends or is async is left out with a console message (0.1.22 rendered no rows).
  • An undefined component (a broken import) or an object as a child fails the render wherever it is, as React's own render does, also inside a layout component.
  • Your components in a Column get only the props you give them; 0.1.22 also passed Elements' internal ones (_config, bodyValues, cells…). Markup they return (a wrapper, a link) renders where 0.1.22 left it out.
  • Markup written straight in a Row or Column is still left out, but a block, Column or Row inside it (a <div> around them) now renders.
  • A block's values._meta.htmlID is now its wrapper's id, which the head's CSS selects (0.1.22 wrote a generated id there, so its hover and link styles missed). One values object with an id, reused on several blocks, gives them all that id. An id or class name given there loses quotes, <, >, & and backticks (an id, spaces too), which could end its attribute.
  • rgb() colors are written as hex and double-quoted font names with single quotes, in the HTML and the design.
  • Plain text breaks the line at a <br> in a heading, link or list item, and decodes entities in headings.
  • renderToJson leaves markup in a Column out, as the HTML does (0.1.22 threw).
  • A component may be called once more on its own (to take its HTML for a Column, or to say why it failed), so side effects belong outside rendering.
  • renderToHtml reads the root's settings through a Provider, StrictMode or Profiler around the template: <UnlayerProvider><Email>… gives the email document. A class template, or a root inside other markup, renders as before, with a console warning that its root's settings (fonts, lang, dir, phone styles) aren't read.
  • Renders are slower. A review measured 2.6× 0.1.22's time on the server on average, about 0.2 ms more per render. Live-preview updates in the browser are 15–30% slower.
  • A textDirection other than ltr/rtl/auto is left out.
  • Text placed straight in a Row or Column logs a console warning, once per process: it isn't kept in the design.
  • A root's lang and fonts are root settings now, so the design's body values no longer copy them (0.1.22 passed them through as unknown props; the editor reads neither there). A font URL given twice is linked once.
  • Row's device props are typed.

Known limits

  • Called inside an app's own react-dom/server renderToString where no second react-dom module shares the app's React (a bundle with React in it, some test runners), Elements renders with the same module, as 0.1.22 did. A call in useMemo or before another hook then fails, as 0.1.22's renderToHtml did there, and renderToJson now does too (0.1.22's walked the tree without rendering); calling them in the component's body works.
  • In Next.js's edge runtime, react-dom can't render with React Server Components' React. renderToHtml fails there, as in 0.1.22, and renderToJson (a tree walk in 0.1.22) now fails too, with an error saying to use the Node.js runtime. Next.js 16 deprecates the edge runtime.
  • An ES module or a bundle on Node 18, or 20 below 20.16 (both past end of life), can't load the second react-dom module.
  • Called inside an app's own renderToString, an email's component reads the page's Context value where the email has no Provider of its own; with one, it reads the email's, and React warns in development ("Detected multiple renderers…"). The page keeps its values. (0.1.22 left such components out.)
  • In a live preview rendered with createRoot, a Context Provider around <Email> doesn't reach the components inside it.
  • In a Next.js Client Component's server render, Context read inside an email worked out there gets the page's value instead of the email's own Provider; the browser render gets it right, so React logs a hydration mismatch. (0.1.22 left such components out of the email.)
  • Pre-existing in 0.1.22 too:
    • table cells take HTML as given;
    • javascript: URLs render as given;
    • Body's linkStyle never applies;
    • Next.js 14 can't bundle Elements for an edge route.

See #61 for the full description and the review history.

Full Changelog: v0.1.22...v0.2.0

@unlayer/migrate 0.1.2

Choose a tag to compare

@github-actions github-actions released this 13 Oct 20:07
eb0a80d

What's Changed

  • @unlayer/migrate 0.1.2: React Email's current demo, and a regression run on real projects by @ivoIturrieta in #67

Full Changelog: migrate-v0.1.1...migrate-v0.1.2

@unlayer/migrate 0.1.1

Choose a tag to compare

@github-actions github-actions released this 13 Oct 17:41
679abd1

What's Changed

  • fix: render without random numbers, so Next.js 16 can prerender emails by @ivoIturrieta in #65
  • @unlayer/migrate 0.1.1: fixes from a field test on real projects by @ivoIturrieta in #66

Full Changelog: migrate-v0.1.0...migrate-v0.1.1

v0.1.22

Choose a tag to compare

@github-actions github-actions released this 22 Sep 11:59
aa3f0ba

What's Changed

New Contributors

Full Changelog: v0.1.21...v0.1.22

v0.1.21

Choose a tag to compare

@github-actions github-actions released this 13 Aug 19:26
a4195f9

What's Changed

New Contributors

Full Changelog: v0.1.20...v0.1.21

v0.1.20

Choose a tag to compare

@github-actions github-actions released this 15 Jul 18:13
adaa68c

What's Changed

  • Add Codecov badge and exclude non-source files from coverage by @adeelraza in #41
  • Add FAQ section to README by @adeelraza in #43
  • fix(react): update exporters and guard browser runtime by @ivoIturrieta in #47
  • Add InvoiceReceipt template and update rendering modes to replace the hardcoded mode:email by @yeabwang in #45
  • fix(ci): support Node 24 releases and fork previews by @ivoIturrieta in #48

New Contributors

Full Changelog: v0.1.19...v0.1.20

v0.1.19

Choose a tag to compare

@github-actions github-actions released this 04 Jul 17:00
44899cc

What's Changed

Full Changelog: v0.1.18...v0.1.19

v0.1.18

Choose a tag to compare

@github-actions github-actions released this 04 Jul 16:55
ae9e346

What's Changed

  • Custom tools: registerElementsTool renders editor custom tools from code by @ivoIturrieta in #37

Full Changelog: v0.1.17...v0.1.18

v0.1.17

Choose a tag to compare

@github-actions github-actions released this 04 Jul 08:41
49bab75

What's Changed

Full Changelog: v0.1.16...v0.1.17