Repository navigation
@unlayer/react-elements 0.3.0: Tailwind, Markdown, code, fonts, async, codegen and editor-parity fixes - #69
Open
ivoIturrieta wants to merge 18 commits into
Open
ivoIturrieta wants to merge 18 commits into
ivoIturrieta wants to merge 18 commits into
Conversation
…nd codegen New entries, each its own import so the main bundle stays within budget: - `/tailwind`: <Tailwind> turns classes into each component's props at render time, so the email gets inline styles and the design stays editable; classes no prop takes become inline styles (HTML only, warned); max-sm: → phone settings, hover: → button hover colors. Container text classes reach the text inside, Row padding classes inset content and card grids get gutters built from borders in the surrounding color. - `/markdown` (Markdown → blocks), `/code` (CodeBlock, CodeInline, highlight.js themes), `/font` (<Font> with fallbacks, URL kept in the design), `/head` (<Head>), `/async` (async templates and components, Suspense, `pretty`), `/codegen` (designToElements: an Unlayer design → readable Elements code; exact round trip on 48 of 49 real designs and fuzzed ones). - Main entry: Link, style/className/skipInText on every component, plain-text options and toPlainText, prop aliases (Menu color, align/textAlign, Heading as/level) with "did you mean" warnings, `values` as ValuesInput (partial, strict keys), rich inline children. Editor parity fixes (HTML and design now agree): UnlayerProvider config applies (render options > root config > Provider > defaults); Paragraph text is stored as typed; exact px image widths; bare zeros written as 0px; Row background-image fullWidth default; the body link color selector; url() values percent-encoded; right-to-left dir/lang on body; a Row whose Column count doesn't match its layout is left out alone, with an error; render errors keep their cause. Row padding stays the editor's outer padding, with a warning when it sits outside a content background. The main bundle budget rises from 118 KB to 126 KB (125,962 bytes now). Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
Guides for Tailwind, Markdown, code blocks, fonts, async templates and codegen; README sections for Link, Head, aliases, the Provider precedence and Row padding; CHANGELOG Unreleased entries. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
CI's main-entry budget goes to 126,000 bytes. The Next.js 14/15/16 integration apps also render a template inside an UnlayerProvider (route handler, Server Component, Client Component and the Pages edge route) and verify the right-to-left HTML and design. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…ding A container with margins around a dark header and footer must stay row-level padding in the design, the outer container in the HTML and `padding` in the TSX, so the gap shows the page background. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
A round trip through the real editor (load, save, export) showed where renderToHtml and the editor's export of renderToJson's design differ: - A Paragraph rendered through the Lexical paragraph exporter (a unitless line height and a <p> wrapper) while its design holds a text block, which the editor exports with a percent line height and no wrapper, so lines with text of another size moved. Both now use the text block, and formatted textJson stays HTML in the design. - A root's `fonts` never reached the design. Families a font URL names now get the URL on their fontFamily, as <Font> does; anything else is warned about once. - The document shell lacked the editor's base stylesheet for each mode. - Images and video thumbnails were sized against the column, not the slot the default 10px block padding leaves, so they ran 20px wide. - A pinned image's percent was rounded up in the HTML and to the nearest in the design. - The design now holds the body's displayMode, which the editor keeps and codegen reads to pick the root. A per-block test checks each block's HTML equals its design's exporter output in all three modes. The bundle budget grows by about 3.9KB. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…check The converted template's fonts are already listed in the report, so the new warning for a root font the design can't hold isn't a lost block. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…Mode Also say that the editor links its own stylesheet for the Google fonts it already lists, with weights 400 and 700. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…ender With async templates under way at once, <Head> and <Font> recorded into the top of the shared collection stack, which can be another template's: one request's head tags and fonts could land in another's email. They now find their template's collection by the render's ids, as Rows and blocks do. Font's pure helpers move to utils/font-design so codegen still imports nothing that renders. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
A component's lone <a> became a Paragraph holding only what text can hold, so a linked image was lost, and an <hr> with a style became a default Divider. Each becomes a block only when the block keeps all of it; anything else stays an HTML block, as in 0.2. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
String.fromCodePoint threw on a reference past U+10FFFF, so untrusted Markdown could make <Markdown> throw and its Column be left out. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #69 +/- ##
==========================================
+ Coverage 96.55% 97.83% +1.27%
==========================================
Files 40 95 +55
Lines 2789 6550 +3761
Branches 795 2277 +1482
==========================================
+ Hits 2693 6408 +3715
- Misses 95 142 +47
+ Partials 1 0 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… hold it A bare <a> in a Column became a Paragraph only when its markup was exactly what text holds, so a link with another attribute (aria-label) was left out, call to action and all. A link whose tags text holds is a Paragraph again (other attributes aside); one holding more (an image), or a rule with a style, is an HTML block as it is, instead of a block that loses part of it. A component's markup still becomes a block only when nothing is lost, and is otherwise its own HTML block. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
ivoIturrieta
marked this pull request as ready for review
October 15, 2026 16:58
…o it The release job bumps a patch unless a newer version is committed, so this would have published 0.2.2 and given every ^0.2 project the changed HTML. And it refuses an Elements outside the published @unlayer/migrate's range (^0.2.0) unless the range is raised in the same change. Committing 0.3.0 and raising migrate's range to ^0.3.0 lets Elements publish as 0.3.0 and migrate republish after it. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
0.2.1 only exported the main entry, so Tailwind, Markdown, Code, Font, Head and Async are new entries in 0.3, with <Link>, style/className/ skipInText, the plain-text options and inline JSX children. The changelog listed few of them, and Tailwind's details as fixes. Also adds the RTL alignment, Column markup and async error changes, and drops mentions of CLI commands that aren't released. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
The changelog went from Unreleased straight to 0.1.0. Each release since has its entry now, from its release notes and pull requests. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
renderToHtml rethrew "[Unlayer] renderToHtml failed: …" without the error it caught, so tools lost the template's stack and line numbers. The rethrown error now carries it as `cause`. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…om's In Next.js App Router (route handlers and Server Components run on two React copies) and anywhere else the copies differ, the async renderers never detected it, so Rows rendered as plain HTML: a web layout with 3 tables instead of 14 and a design with no rows, with only a warning. They now probe as the sync path does and record without useId, kept apart per render by AsyncLocalStorage (one at a time where there's none). A render whose Rows still weren't collected now throws instead of returning a broken email, sync and async alike; empty, text-only and head-only templates don't. The bundle budget grows by 1 KB for it. Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
An async route handler and an async Server Component in the Next 15 and 16 apps, checked to give the synchronous renderers' tables and rows. Both failed before the fix (3 tables, no rows). Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The next
@unlayer/react-elementsrelease (0.3.0): formats developers already use, a design → code converter, and fixes so the HTML and the design that opens in the Unlayer editor always agree. Nothing here is breaking.New entries (separate imports, so the main bundle stays small)
/tailwind<Tailwind>: classes become each component's props at render time, so the email gets inline styles and the design stays editable. Phone variants become phone settings,hover:becomes button hover colors, container text classes reach the text inside, and card grids get gutters./markdown<Markdown>: Markdown becomes editable blocks./codeCodeBlock/CodeInlinewith highlight.js themes./font<Font>: web fonts with fallbacks. The URL is kept in the design./head<Head>: custom head tags./asyncprettyoutput./codegendesignToElements(design): an Unlayer design becomes readable Elements code. The round trip is exact on 48 of 49 real designs and on fuzzed ones.Main entry
Link.style,classNameandskipInTexton every component.toPlainText.color,align/textAlign, Headingas/level. Close misspellings get "did you mean" warnings.valuesaccepts partial objects with strict keys (ValuesInput).HTML / editor parity fixes
<UnlayerProvider config>now applies. The most specific setting wins: render options, then the root'sconfig, then the Provider, then defaults. Safe in Server Components and on the edge.textis stored as typed, so the editor no longer formats markup the email shows literally.0pxin designs.fullWidthdefault agrees between HTML and design.url()values are percent-encoded.dir/langon the body.cause.<p>wrapper), so lines with mixed text sizes no longer shift; a root'sfontsreach the design; the document has the editor's base stylesheet for each mode; images and video thumbnails are sized in the block's slot (the default 10px padding), not the column; a fixed-width image's percent is the same in HTML and design; and the design recordsdisplayMode, so code generated from it picks the right root. A per-block test checks every block's HTML equals the editor's export of its design, in all three modes.Row padding keeps the editor's meaning (outer padding, in email, web and print). Elements now warns when it sits outside a content background; use Column padding to inset. Tailwind
p-*on a Row insets, as CSS does.Validation
test:csp,test:browser-runtime,test:e2e,test:fidelityandtest:differentialpass.test:visualmatches on 138 stories × 2 viewports. The baseline was regenerated after attributing every changed render to intended changes (exact image widths, the Provider fix, the text block's markup, images sized in their slot).Release notes
packages/react/package.json, which the release job publishes instead of its default patch. Without it, this would ship as 0.2.2 to every^0.2project.@unlayer/migrate's Elements range goes to^0.3.0here. The release job refuses an Elements outside the published migrate's range, so migrate then republishes after it (0.1.3, the same converter) and asks projects on 0.2.x to upgrade first.<Link>,style/className, the plain-text options, inline JSX), what changed, and the history from 0.1.1 to 0.2.1.@unlayer/react-elements-previousalias to 0.3.0.📖 Storybook Preview: https://unlayer.github.io/elements/pr/69/