Repository navigation
Releases: unlayer/elements
Release list
v0.2.1
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
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,textDirectionandlang. 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
textprop, Paragraph'shtmlor an<Html>block. - So does template syntax with
<or>, such as{% if a > b %}. - Write the inbox padding characters themselves, not
‌.
- Heading and Button children,
- Menu text in emails escapes markup and double quotes, also inside a template tag (the text is in an
aria-labeltoo), 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).
renderToJsonkeeps 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.htmlIDis 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). Onevaluesobject 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. renderToJsonleaves 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.
renderToHtmlreads 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
textDirectionother thanltr/rtl/autois 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
langandfontsare 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/serverrenderToStringwhere 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 inuseMemoor before another hook then fails, as 0.1.22'srenderToHtmldid there, andrenderToJsonnow 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.
renderToHtmlfails there, as in 0.1.22, andrenderToJson(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
linkStylenever 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
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
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
What's Changed
- feat(examples): add interactive Next.js App Router example by @Cypher-Aura-19 in #53
- docs: add email client compatibility notes by @Shreyans-704 in #55
- Update document rendering and pagination parity by @lucasbesen in #57
New Contributors
- @Cypher-Aura-19 made their first contribution in #53
- @Shreyans-704 made their first contribution in #55
- @lucasbesen made their first contribution in #57
Full Changelog: v0.1.21...v0.1.22
v0.1.21
What's Changed
- feat(demo): add newsletter web archive example (#22) by @rajarshidattapy in #51
- Decode common named HTML entities in renderToPlainText output by @eastagiletracker in #54
New Contributors
- @rajarshidattapy made their first contribution in #51
- @eastagiletracker made their first contribution in #54
Full Changelog: v0.1.20...v0.1.21
v0.1.20
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
What's Changed
- renderToHtmlParts: expose granular css/js/tags by @ivoIturrieta in #38
Full Changelog: v0.1.18...v0.1.19
v0.1.18
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
What's Changed
- docs(elements): point README to the unlayer-elements skill by @ivoIturrieta in #29
- Fix/paragraph margins and full document html by @ivoIturrieta in #36
Full Changelog: v0.1.16...v0.1.17