npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-tailwind-email-editor

v0.2.4

Published

Customizable React drag-and-drop email template builder with HTML export, mobile phone preview, zoom controls, TypeScript and scoped CSS

Readme

React Email Editor

A customizable drag-and-drop HTML email template builder for React

Build newsletters, campaigns, invoices, and transactional email templates with a visual editor. Bring your own design system, register custom blocks, preview the complete email inside a phone frame, and export table-based HTML.

React 18 / 19 · TypeScript · Scoped CSS · Custom blocks · Desktop & mobile preview

Live demo · Sample project · Quick start · Screenshots · Props · Extensions · npm package

Package name: react-tailwind-email-editor · Current version: 0.2.0

The existing npm name is retained for compatibility. Tailwind configuration and shadcn installation are not required.

React Email Editor workspace with a component toolbox and branded email canvas

Live demo

Try the editor in the hosted Vercel demo.

Open live demo →

For the local version, follow Run the locally installed demo.

Features

| Capability | What you can do | Explore | | --- | --- | --- | | Visual email building | Drag blocks onto the canvas, edit their properties, reorder, duplicate, and undo changes | Built-in blocks | | Host branding | Override colors, typography, radius, spacing, and panel dimensions with scoped CSS | Styling guide | | Phone preview | View the whole email in a phone mockup, fit its width, or zoom into details | Mobile preview | | Custom blocks | Register Craft components, editable settings, nested layouts, and HTML renderers | Plugin guide | | Replaceable UI | Supply your own toolbar, toolbox, properties panel, dialogs, or selection wrapper | Slot APIs | | Drafts and templates | Save serialized state, restore documents, and offer reusable template libraries | Persistence | | HTML export | Preview, copy, or download table-based email HTML with inline styles | Export API | | Invoice editing | Edit item descriptions, quantities, unit prices, currency, and totals | Invoice screenshot | | Typed integration | Use exported configuration types, callbacks, context, and renderer utilities | Props reference |

Contents

Quick start

npm install react-tailwind-email-editor @craftjs/core react react-dom

Peer dependencies: React / React DOM 18 or 19, and Craft.js ^0.2.12. Keep a single copy of React and Craft in the host application, especially when registering custom blocks. UI dependencies are included by the package.

import { EmailEditor } from "react-tailwind-email-editor";
import "react-tailwind-email-editor/styles.css";

export default function App() {
  return <EmailEditor darkMode={false} style={{ height: "80vh" }} />;
}

The stylesheet must be imported once. The legacy react-tailwind-email-editor/dist/style.css path also works. ESM, CommonJS, and TypeScript declarations are provided. For server-rendered frameworks, render the editor in a client component; HTML import and interactive editing use browser APIs.

Try it: drag a block into the canvas → select it → edit Properties → open Preview → switch to Mobile → choose Fit email → Export your HTML.

Screenshots

These screenshots were captured from the standalone Vite React app using the locally installed package. Click an image to view it at full size. Expand a section to inspect a specific workflow.

The Mobile tab presents the template inside a phone frame, with Fit email, Fit width, zoom out, and zoom in controls.

Email template displayed inside a phone mockup with fit and zoom controls

For long newsletters, Fit email provides a complete overview from header to footer.

Complete long email fitted into the mobile phone screen

Configure mobile preview →

Selected text block with editable text, font size, font weight, color, and alignment

Explore custom settings components →

Invoice settings with description, quantity, unit price, and remove controls contained within each card

View invoice block props →

React email editor in dark mode with a light email canvas

Customize the theme →

HTML export dialog with copy and download actions

Connect export to your application →

Back to top ↑

Style and theme overrides

All package classes and tokens begin with ree-. There is no global reset, body styling, generic .flex utility, or application-wide .dark class. Each editor has an independent theme. Portaled dialogs, menus, and color pickers inherit the owning editor's styles.

Import your override stylesheet after the package stylesheet:

<EmailEditor
  className="campaign-editor"
  darkMode={false}
  theme={{
    colors: {
      primary: "173 80% 27%",
      primaryForeground: "0 0% 100%",
    },
    borderRadius: 10,
    fontFamily: "Inter, system-ui, sans-serif",
    sidebarWidth: 310,
    panelPadding: 20,
    canvasPadding: 32,
    modalPadding: 24,
    controlHeight: 36,
    paperWidth: 600,
    paperMinHeight: 800,
    paperBackground: "#ffffff",
  }}
/>
.campaign-editor {
  --ree-primary: 173 80% 27%;
  --ree-radius: 10px;
  --ree-panel-padding: 20px;
  height: 760px;
}
.campaign-editor .ree-toolbar { padding: 16px 20px; }
.campaign-editor .ree-toolbox-item { border-radius: 12px; }
.campaign-editor .ree-modal-title { font-size: 20px; }

Colors in theme.colors and CSS tokens use HSL channels, such as "243 75% 59%", without an hsl() wrapper. Paper colors use regular CSS colors such as #ffffff. Numeric spacing and radius props use pixels, including zero.

Theme props set inline tokens and take precedence over stylesheet tokens. theme.cssVariables overrides generated theme tokens; style has final precedence. Avoid specifying the same token in both CSS and theme props unless that precedence is intentional. Dark mode supplies defaults for neutral tokens; use .campaign-editor[data-ree-theme="dark"] to override those defaults in CSS.

| CSS variable | Default / purpose | | --- | --- | | --ree-primary, --ree-primary-foreground | Accent and readable accent text | | --ree-background, --ree-foreground | Editor surface and text | | --ree-muted, --ree-muted-foreground | Subtle surfaces and text | | --ree-border, --ree-destructive | Borders and destructive actions | | --ree-panel-background | Optional panel surface, falls back to background | | --ree-canvas-background | Optional canvas surface, falls back to muted | | --ree-font-family | System sans-serif stack | | --ree-radius | 8px | | --ree-sidebar-width | 304px | | --ree-panel-padding | 20px | | --ree-canvas-padding | 32px | | --ree-modal-padding | 24px | | --ree-control-height | 36px for standard buttons and inputs; compact toolbar buttons use 32px | | --ree-z-index | 1000 for dialog overlays |

Stable CSS hooks: .ree-editor, .ree-toolbar, .ree-sidebar, .ree-canvas, .ree-paper, .ree-toolbox, .ree-toolbox-item, .ree-settings, .ree-button, .ree-popover, .ree-modal, .ree-modal-header, .ree-modal-title, .ree-modal-body, .ree-modal-close. Other prefixed classes are internal layout helpers and may change between versions.

The default editor height is 720px, with scrolling inside the canvas and sidebar. Set style.height for your host layout. Below a 720px viewport width, the sidebar stacks above the canvas. The email paper retains its configured width and scrolls horizontally, preserving email layout. Host styles targeting all elements, !important, or transformed/clipped ancestors can still affect embedded content; use the scoped hooks to resolve host-specific integration needs.

Custom blocks and plugins

The components, htmlRenderers, and slots props are the plugin interface. Register blocks with stable names so saved templates remain compatible. Custom blocks use the host's Craft peer dependency:

import { useNode } from "@craftjs/core";
import { EmailEditor } from "react-tailwind-email-editor";

function Notice({ text = "Welcome" }: { text?: string }) {
  const { connectors: { connect, drag } } = useNode();
  return <div ref={element => { if (element) connect(drag(element)); }}
    style={{ padding: 24, background: "#eef2ff" }}>{text}</div>;
}
function NoticeSettings() {
  const { actions: { setProp }, text } = useNode(node => ({ text: node.data.props.text as string }));
  return <label>Notice text<input value={text ?? ""}
    onChange={event => setProp((props: { text?: string }) => { props.text = event.target.value; })} /></label>;
}
Notice.craft = { props: { text: "Welcome" }, related: { settings: NoticeSettings } };

export function MyEditor() {
  return <EmailEditor
    components={{
      Notice: { component: Notice, label: "Notice", category: "Custom" },
    }}
    htmlRenderers={{
      Notice: (_name, props) => {
        const text = String(props.text ?? "").replace(/[&<>"']/g, char =>
          ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" } as Record<string, string>)[char]!);
        return `<table role="presentation" width="100%"><tr><td style="padding:24px;background:#eef2ff">${text}</td></tr></table>`;
      },
    }}
  />;
}

Provide a craft.related.settings component using useNode().actions.setProp to make a custom block editable. createElement in a registry entry can supply default props or a nested Craft Element tree. Supply a matching HTML renderer for every custom block; use inline styles and email-compatible markup. Hosts are responsible for validating custom HTML, URLs, and data from untrusted sources. Raw HTML blocks render the supplied HTML in the editing canvas; use trusted or sanitized content.

Replace UI sections with your own React components:

<EmailEditor slots={{
  toolbar: MyToolbar,
  toolbox: MyToolbox,
  settingsPanel: MySettings,
  settingsEmptyState: MyEmptyState,
  exportDialog: MyExportContent,
  previewDialog: MyPreviewContent,
  renderNode: MySelectionWrapper,
}} />

All slot prop types are exported. Toolbar slots receive onExport / onPreview functions returning the current HTML, onClear, title, and logo. Export and preview slots replace the content inside the built-in dialog and receive html and onClose. Custom settings slots receive selection details and available actions. The useEditorConfig hook exposes the merged configuration inside slots and blocks.

Save, restore, and export

<EmailEditor
  initialState={savedCraftJson}
  callbacks={{
    onChange: json => saveDraft(json), // Debounce persistence in the host if needed.
    onExport: html => storeExport(html),
    onTemplateLoad: id => console.log(id),
    onClear: () => console.log("Cleared"),
    onColorChange: hsl => console.log(hsl),
  }}
/>

initialState and initialHtml load on mount; remount with a new React key to load another initial document. initialState takes priority over initialHtml; defaultContent supplies JSX when neither is set. Initial state changes may invoke onChange, as do subsequent Craft changes. Export produces table-based HTML with inline styles, independent of the editor CSS. Preview is sandboxed. Test exported emails in your target email clients before sending.

Mobile phone preview and zoom

Preview → Mobile displays a phone mockup with a status bar, screen, and home indicator. Fit email shows the complete template at once, including fixed-width email tables; Fit width fills the screen horizontally and lets you scroll vertically. Zoom in/out switches to a custom scale, and the percentage shows the current scale. Fit email restores the overview. The phone resizes with the dialog, including on narrow browser screens.

<EmailEditor preview={{
  initialDevice: "mobile",
  mobileWidth: 375,
  mobileHeight: 667,
  mobileZoom: "fit",
  showZoomControls: true,
}} />

| EditorPreviewOptions field | Type | Default / behavior | | --- | --- | --- | | initialDevice | "desktop" \| "mobile" | "desktop"; applied when the preview opens | | mobileWidth | number | 375 logical CSS pixels | | mobileHeight | number | 667 logical CSS pixels | | mobileZoom | "fit" \| "width" | "fit"; initial mobile zoom mode | | showZoomControls | boolean | true; false hides the zoom toolbar |

Phone dimensions are clamped to 240–1600 CSS pixels; invalid/non-finite values use the defaults. Fit modes automatically recompute after resizing and image/font loading. Custom zoom ranges from 5% to 200%. Long emails can appear very small in Fit email; use Fit width or zoom in to read them.

Mobile preview is a browser rendering and scaling aid, not an emulation of a specific mail client. Responsive templates render at the configured width; fixed-width templates expand to their content width before scaling so their right edge stays visible. Scripts, forms, popups, and top-level navigation remain disabled. The mobile iframe permits same-origin access only so the editor can measure document dimensions; email links do not navigate the mobile preview. Desktop preview retains its fully restricted sandbox. Exported HTML is unchanged by the phone frame or zoom settings.

Additional CSS hooks: .ree-mobile-preview, .ree-preview-zoom, .ree-preview-scale, .ree-phone-stage, .ree-phone, .ree-phone-status, .ree-phone-screen, .ree-phone-home, .ree-preview-caption. Override phone decoration and surrounding spacing through these classes; use the preview options for logical viewport dimensions.

Props

| Prop | Type | Default | | --- | --- | --- | | components | ComponentRegistry | Built-ins merged with your entries | | replaceBuiltins | boolean | false | | theme | EditorTheme | Default colors, spacing, 600 × 800 paper | | preview | EditorPreviewOptions | Desktop tab; mobile overview in a 375 × 667 phone | | slots | EditorSlots | Built-in UI | | templates | EmailTemplate[] | []; each entry has id, name, serialized data, optional description/icon | | callbacks | EditorCallbacks | {} | | initialState | string | Serialized Craft state | | initialHtml | string | HTML to import on mount | | defaultContent | ReactNode | Empty Paper canvas | | title / logo | string / ReactNode | "Email Editor" / none | | htmlRenderers | Record<string, HtmlRenderer> | {} | | showToolbar / showToolbox | boolean | true | | darkMode | boolean | true; responds to prop changes and can be toggled in the toolbar | | className / style | string / CSS properties with --ree-* tokens | None |

Built-in blocks include header, footer, text, image, button, container, divider, spacer, invoice table, social links, countdown, promo code, testimonial, video placeholder, variable text, icon list, and raw HTML. Paper, Toolbox, SettingsPanel, RenderNode, the default registry, types, and HTML exporter are also exported for integrations.

Complete theme API

| EditorTheme field | Type | Default | | --- | --- | --- | | colors | EditorThemeColors | Independent light/dark neutral colors and indigo accent | | borderRadius | number | 8 px | | fontFamily | string | System sans-serif stack | | sidebarWidth | number | 304 px | | panelPadding | number | 20 px | | canvasPadding | number | 32 px; responsive default 16 px on narrow screens | | modalPadding | number | 24 px; responsive default 16 px on narrow screens | | controlHeight | number | 36 px; compact buttons retain their compact size | | paperWidth | number | 600 px, editing canvas width | | paperMinHeight | number | 800 px | | paperBackground | string | "#ffffff"; a block's saved background takes precedence | | cssVariables | Partial<Record<`--ree-${string}`, string>> | Additional/overriding package CSS tokens |

Every EditorThemeColors field is an optional HSL-channel string: primary, primaryForeground, panelBackground, canvasBackground, border, muted, mutedForeground, destructive, foreground, and background. See the token table above for their roles.

Registry, template, callback, and slot APIs

Each components key maps to an EditorComponentConfig:

| Field | Type | Required / behavior | | --- | --- | --- | | component | React.ComponentType<any> | Required; Craft-enabled component | | label | string | Required; toolbox name | | description | string | Optional; toolbox description | | icon | Lucide icon or component accepting className | Optional | | category | string | Optional; defaults to "Other" | | createElement | () => React.ReactElement | Optional; construct initial props or a nested canvas |

EmailTemplate has required id: string, name: string, and data: string (serialized Craft JSON), plus optional description: string and icon: LucideIcon. Templates must reference registered component names.

| EditorCallbacks member | Signature | Trigger | | --- | --- | --- | | onChange | (serializedState: string) => void | Craft document changes; initial document may also trigger it | | onExport | (html: string) => void | Built-in Export action | | onTemplateLoad | (templateId: string) => void | Successful template load | | onClear | () => void | Clear canvas action | | onColorChange | (primaryHsl: string) => void | Built-in primary color selection |

| EditorSlots member | Props supplied to the replacement | | --- | --- | | toolbar | ToolbarSlotProps: onExport(): string, onPreview(): string, onClear(): void, optional title, logo | | toolbox | ToolboxSlotProps: merged components, categories: string[] | | settingsPanel | SettingsPanelSlotProps: optional selectedId, selectedName, selectedSettings, onDelete, onDuplicate, onMoveUp, onMoveDown; shown when a node is selected | | settingsEmptyState | No props; shown when nothing is selected | | renderNode | { render: React.ReactNode }; wrap or display the supplied node | | exportDialog | ExportDialogSlotProps: html, onClose() | | previewDialog | PreviewDialogSlotProps: html, onClose(), optional preview: EditorPreviewOptions |

Action props on settings slots have signature () => void; unavailable actions can be undefined. A custom toolbar owns its own dialogs and event handling: its onExport and onPreview helpers return HTML and do not open the default dialogs or automatically invoke the built-in Export callback.

Extension recipes and public exports

  • Custom email blocks: add a registry entry with craft.props and craft.related.settings; register a matching HTML renderer.
  • Nested layouts: use createElement with Craft Element canvases and stable registry keys. Use exported Container and Paper as building blocks.
  • Custom properties and uploads: implement a settings component using useNode; an image uploader can save a URL to the node's src prop after your host handles storage.
  • Application toolbars and actions: use the toolbar slot and Craft's useEditor for save/load, undo/redo, or custom commands inside the editor context.
  • Template libraries: pass serialized templates with stable IDs; persist state through onChange and restore with initialState.
  • Email merge tags: use VariableText, inspect exported AVAILABLE_VARIABLES, or register a block for your host's variable schema. Resolve actual recipient values in your host's send pipeline.
  • Custom HTML and export: use exported RawHtml, or htmlRenderers with signature (typeName, props, childrenHtml, linkedNodes?) => string | null. Returning null falls back to the built-in renderer. Escape user text and validate URLs in custom renderers.
  • Custom previews: replace previewDialog to integrate a different device frame or your own rendering service; the slot receives HTML and the preview configuration.
  • Host design systems: use theme tokens and CSS hooks, or replace UI sections through slots.

Public utilities are generateEmailHtml(nodes, customRenderers?), importHtmlToState(html) (browser DOM required), buildResolver(registry), and getCategories(registry). DEFAULT_COMPONENTS exposes the built-in registry. useEditorConfig() reads the current configuration; EditorProvider provides it for advanced compositions. Craft editor actions still require a Craft Editor ancestor; the configuration provider alone does not create an editing canvas. Run npm run docs:api when block prop definitions change to regenerate the detailed block reference.

All main prop/configuration interfaces, callback interfaces, registry types, template types, slot prop types, and HtmlRenderer are exported from the package root. See the built-in block prop reference for every block's fields and rendering defaults. See the demo README for local install and verification commands.

React email builder use cases

Embed this customizable React email editor in a SaaS campaign studio, newsletter builder, transactional email template designer, or invoice email builder. It combines a visual drag-and-drop email builder, HTML email export, responsive mobile email preview, TypeScript configuration, custom Craft.js blocks, and scoped CSS for white-label integrations. No Tailwind setup is required by the host application.

Upgrade from 0.0.20

  1. Install 0.2.0 and its Craft peer dependency. The npm package name is unchanged.
  2. Import react-tailwind-email-editor/styles.css once. The previous CSS path remains supported.
  3. Remove editor-specific Tailwind content scans, plugins, and shadcn setup from your application. Keep them if your host app uses them independently.
  4. Replace editor overrides using unprefixed utilities, --primary, --background, or .dark with --ree-* tokens and the hooks above.
  5. Set the editor height explicitly if your layout relied on the former full-screen height. The default is now 720px.
  6. darkMode={true} now correctly starts dark. Saved Craft JSON and registry names retain the existing format.

Run the locally installed demo

Standalone Vite React consumer

Browse the sample project on GitHub →

Use the sample repository to inspect the Vite React integration and see how the editor and its stylesheet are imported into a host application.

The separate consumer project is my_project/email_editor_test, beside Email-Editor-Package. It uses its own package.json, lockfile, Vite entry point, React source, and installed dependencies. Its dependency points to ../Email-Editor-Package/react-tailwind-email-editor-0.2.0.tgz; the UI imports EmailEditor and the stylesheet by package name.

# From my_project/email_editor_test
npm install
npm run build
npm run dev -- --port 5175 --strictPort --force

Open http://127.0.0.1:5175/. After changing the package, run npm pack in the package repository, reinstall the tarball in this consumer, and restart Vite with --force. Use npm test in the consumer to run its browser checks; set EDITOR_DEMO_PORT=5175 to reuse the running server. The in-repository example below also documents how to reproduce the integration on another computer.

Repository example

From the package root:

npm ci
npm run typecheck
npm test
npm pack
cd examples/host-app
npm install
npm run build
npm run dev -- --port 5173

Open http://127.0.0.1:5173. The demo installs the actual 0.2.0 tarball, rather than importing package source or using a symlink. It includes host branding, light/dark controls, a custom Craft block and HTML renderer, host utility collision checks, and a surrounding form. After package changes, run npm pack again and reinstall the tarball in the demo (npm install ../../react-tailwind-email-editor-0.2.0.tgz --force).

Browser integration checks live in examples/host-app/tests; run npx playwright test there. They use installed Microsoft Edge by default. Set EDITOR_BROWSER_CHANNEL=chromium and run npx playwright install chromium to use Playwright's Chromium instead. Set EDITOR_DEMO_PORT if the default demo port is occupied. See CHANGELOG.md for release changes.

Frequently asked questions

No. Import the package stylesheet once. The editor uses scoped plain CSS, and its UI dependencies ship with the package. Your application can use its own CSS framework independently. See Quick start.

Yes. Use the theme prop, --ree-* CSS variables, and the documented class hooks. Use slots to replace entire UI sections. See Style and theme overrides and Slot APIs.

Make sure your application imports react-tailwind-email-editor/styles.css. Import your own override stylesheet afterward. See Quick start.

initialState and initialHtml apply on mount. Change the editor's React key when loading a different initial document, or use Craft actions inside a custom toolbar. See Save, restore, and export.

It provides a browser-rendered preview and scaling controls. Email clients have different rendering engines; validate exported HTML in the clients your recipients use. See Mobile preview behavior.

Open the live demo to try the editor. For local setup and package integration, see Run the locally installed demo.

Support and releases

Back to top ↑

License

ISC, as declared in package.json.