Skip to main content

Quickstart

Empty Node project to a rendered HTML string, in three steps.


1. Install

npm install @unlayer/react-elements react react-dom

If your project doesn't have a tsconfig.json yet, create one with the modern JSX runtime. The .tsx samples below assume it, and won't compile without:

tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2020",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}

See Installation for peer dependencies and tsconfig notes.


2. Build Your First Email

Create welcome-email.tsx:

import {
Email,
Row,
Column,
ColumnLayouts,
Heading,
Paragraph,
Button,
} from '@unlayer/react-elements';

export function WelcomeEmail() {
return (
<Email backgroundColor="#f0f0f0" contentWidth="600px">
<Row
layout={ColumnLayouts.OneColumn}
backgroundColor="#ffffff"
padding="20px"
>
<Column>
<Heading
headingType="h1"
fontSize="24px"
fontFamily={{ label: 'Arial', value: 'arial,helvetica,sans-serif' }}
>
Welcome!
</Heading>
<Paragraph fontSize="14px">Thanks for signing up.</Paragraph>
<Button
href="https://example.com"
backgroundColor="#0879A1"
color="#ffffff"
>
Get Started
</Button>
</Column>
</Row>
</Email>
);
}

The structure is always wrapper → Row → Column → content. Layout Components explains why.


3. Render to HTML

Create render.tsx:

import { renderToHtml } from '@unlayer/react-elements';
import { WelcomeEmail } from './welcome-email';

const html = renderToHtml(<WelcomeEmail />);
console.log(html);

Run it:

npx tsx render.tsx

A complete email-safe HTML document prints to stdout. Pipe it to a file, send it through your SMTP provider, hand it to a PDF engine. It's a plain string.

💡 For real email sending, prefer renderToHtmlParts over renderToHtml. It returns { head, body } separately, so the <style> block (hover effects, responsive media queries) lands in <head> where email clients expect it. See Rendering API.

A footgun worth knowing about

If you pass a config override to renderToHtml, the override is silently dropped:

// Looks fine. Does nothing.
renderToHtml(<WelcomeEmail />, { cdnBaseUrl: '...' });

That's because the config lands on WelcomeEmail, which doesn't forward it. Either render the wrapper directly:

renderToHtml(<Email>...</Email>, { cdnBaseUrl: '...' });

Or have WelcomeEmail accept and forward config to <Email>. Configuration covers both shapes.


Two-Column Variant

Switch the layout to TwoEqual and add a second <Column>:

<Row layout={ColumnLayouts.TwoEqual} backgroundColor="#ffffff" padding="20px">
<Column>
<Heading
headingType="h1"
fontSize="24px"
fontFamily={{ label: 'Arial', value: 'arial,helvetica,sans-serif' }}
>
Hello World
</Heading>
</Column>
<Column>
<Paragraph html="Welcome to our newsletter!" fontSize="14px" />
</Column>
</Row>

The column count must match the layout. TwoEqual wants exactly two columns; mismatch throws at validation time. See Limitations for the other common ways to trip up.


What's Next

  • Wrappers — switch from Email to Page or Document, or drop down to Body.
  • Configuration — merge tags, CDN base URL, text direction.
  • Rendering APIrenderToHtml, renderToHtmlParts, renderToPlainText, renderToJson, renderRowToJson.