Skip to content

@unlayer/elements-preview: a local preview for Elements templates - #72

Open
ivoIturrieta wants to merge 6 commits into
feat/react-elements-0.3from
feat/elements-preview
Open

ivoIturrieta wants to merge 6 commits into
feat/react-elements-0.3from
feat/elements-preview

Conversation

@ivoIturrieta

@ivoIturrieta ivoIturrieta commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on the 0.3.0 library PR (#69): it shows only the preview. Merge it after #69, with its base changed to main first. The repo's tests only run on pull requests into main, so CI starts here once the base is main; until then, the validation below was run locally.

What

@unlayer/elements-preview 0.1.0 is a local preview for Elements templates, like React Email's email dev. It's a dev dependency of a project: npm install -D @unlayer/elements-preview && npx elements-preview dev.

  • dev:
    • previews a folder of templates with live reload, rendering them with the project's own Elements and React (Vite SSR with the project as root, so its imports, aliases and React resolve as in the app);
    • one top bar with Preview · Editor · Code, desktop/phone, dark mode, Checks and Export;
    • the Editor view opens the design in the Unlayer editor;
    • the checks drawer covers render problems, email-client support with source lines (caniemail), and links and images.
  • export: HTML, plain text and design JSON files.
  • build / start: a static preview to share or host. A build replaces its previous build's files (listed in .elements-preview-build.json) and leaves others alone; it refuses a non-empty folder that isn't a preview build.
  • check [--json]: a "to fix" / "to check" report with exit codes, for agents and CI.
  • screenshot: desktop, phone and dark PNGs using the system browser (puppeteer-core; no download).
  • render --json: one template's HTML and design.
  • Copy for AI: turns a template and its problems into a prompt.
  • Security: binds to localhost and checks Host and Origin.

It's a package of its own, not part of the Unlayer CLI: it has to run in the project, with the project's Elements and React, and its dependencies (Vite, a Chrome driver, email-client data: about 110 MB) would quadruple the CLI's install for everyone.

Publishing

  • Peers: @unlayer/react-elements >= 0.2.0 and React 18 or 19.
  • The first version is published by hand (npm's trusted publishing needs the package to exist), tagged preview-v0.1.0, with its trusted publisher added. After that, the new publish-preview job in publish.yml publishes a change under packages/preview after a reviewer approves, without waiting for an Elements release.

Validation

  • preview 87 tests and tsc pass.
  • End to end with Elements 0.3.0 in a fresh project, with React 19 and React 18 (identical HTML and designs), on the showcase templates (Tailwind, fonts, Markdown, code, shared components) and a template broken on purpose: check --json (findings, source line, exit codes), render --json (designs carry displayMode, paragraphs are the editor's text block), export, screenshot --all (PNGs looked at), build + start.
  • dev in a browser: live reload, Preview / Editor / Code, desktop/phone, dark mode, checks, the Editor view in the Unlayer editor with local images; no console errors.

ivoIturrieta and others added 6 commits October 16, 2026 08:38
…ments templates

`elements-preview dev` previews a folder of templates in the browser with live reload, rendering
them with the project's own Elements and React (Vite SSR with the project as root, so its
imports, aliases and React resolve as in the app). The UI shows desktop and phone widths, dark
mode as Gmail and Apple Mail apply it, the code, an Editor view that opens the design in the
Unlayer editor, and a checks drawer: render problems, email-client support with source lines
(caniemail), and links and images.

Commands: dev, export (HTML, plain text, design JSON), build and start (a static preview to
share), check (--json, exit codes for agents and CI), screenshot (desktop, phone, dark PNGs with
the system browser) and render (--json). Copy for AI turns a template and its problems into a
prompt. Binds to localhost and checks Host and Origin.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
Like migrate: the first version is published by hand (npm's trusted
publishing needs the package to exist), tagged preview-v<version>; after
that, a change under packages/preview publishes it after a reviewer
approves. It doesn't wait for Elements: it renders with the project's
own Elements, a peer dependency.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
A template's error in a build said to fix the file and save, as in dev,
but a build never updates: it now says to build the preview again.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
The checks' labels counted every finding and named them by the worst
("4 to fix" for 3 to fix and 1 to check). They now say both.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
An empty templates folder suggested `npx @unlayer/cli elements init`,
which the released CLI doesn't have, and the READMEs named its
`unlayer elements` commands. The empty folder now says how to write a
template, and the READMEs give the preview's own commands. The README
also says what the dark-mode preview doesn't flip: a color set only by
a stylesheet.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
…rnings

- A build records the files it writes (.elements-preview-build.json) and
  removes the previous build's before writing, so a deleted template's
  source and PreviewProps don't stay in a hosted build. It refuses, with
  exit 2, a non-empty folder that isn't a preview build.
- Two templates that would write the same file (welcome.jsx and
  welcome.tsx) stop export and screenshot with exit 2, naming both,
  instead of one silently replacing the other.
- A URL that doesn't decode (/static/%) is a finding, not an error that
  ends check or build; the dev server answers it with 400, and a static
  build's link to it opens the first template.
- Only Elements' own console.error messages are "to fix". React's
  development warnings (a list without keys) and a template's own errors
  are "to check", as the README says, so check no longer fails on them.
- check on a folder with no templates exits 2.
- export --plainText fails a template whose plain text couldn't be made
  instead of writing an empty file.
- The dev server reuses its last render for the checks until a file
  changes: one render per save instead of three.

Co-Authored-By: Claude Code (Claude Opus 5.5 xhigh) <noreply@anthropic.com>
@ivoIturrieta
ivoIturrieta marked this pull request as ready for review October 16, 2026 15:24

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant