About unlayer-elements
unlayer-elements is a code-first React component library for developers generating emails, web pages, and documents with JSX. It renders HTML or Unlayer design JSON that can be opened in the visual editor.
Key features
- 01React components for email, web, and document layouts
- 02Render JSX to HTML or design JSON
- 03Generated designs editable in Unlayer editor
- 04Server-side rendering support
- 05Components for common content blocks
Use cases
- 01Generate email templates programmatically
- 02Create responsive web page markup
- 03Produce print- or PDF-oriented documents
- 04Let teammates edit code-built designs visually
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ unlayer/ unlayer-skills --skill unlayer-elementsAfter installing, type/unlayer-elementsin Claude Code to confirm it loaded.
Bundled filesreferences/component-reference.mdreferences/patterns.md
Details
From the project READMEBuild with Unlayer Elements (code-first)
Overview
@unlayer/react-elements is a set of React components for building emails, pages, and documents in code — no editor required. Write JSX once and render it to:
- email — table-based HTML (Outlook/Gmail/Yahoo safe)
- web — responsive div/flexbox HTML
- document — print / PDF-optimized HTML
The output is a faithful reproduction of what the Unlayer editor produces, so designs round-trip into the editor as JSON: renderToJson() returns design JSON that the visual editor opens with loadDesign(), letting non-developers edit templates you built in code. Full SSR support (Next.js, Remix, renderToString).
When to use this skill vs the editor
| You want to… | Use |
|---|---|
| Generate/assemble templates in code (AI generation, programmatic emails, design systems) | this skill (@unlayer/react-elements) |
| Build templates in code and let teammates edit them visually | this skill (renderToJson()) + unlayer-integration (loadDesign()) |
| Embed a drag-and-drop visual editor in your app | unlayer-integration |
| Export HTML/PDF from a saved design / Cloud API | unlayer-export |
Install
npm view @unlayer/react-elements version # verify the published version first
npm install @unlayer/react-elements
This skill targets
@unlayer/react-elements0.1.11+ (npm view @unlayer/react-elements versionto check) and uses only forms verified against it. The natural forms below — string/number sizes, object or stringfontFamily, factored-out style objects — all type-check on 0.1.11+; on older versions, upgrade.
Mental model — the structure is strict
<Email> | <Page> | <Document> ← root wrapper, sets the render mode
<Row layout={ColumnLayouts.X}> ← layout container; column count MUST match the layout
<Column> ← only Columns go directly in a Row
<Heading /> <Paragraph /> ← items only inside Columns (no nesting)
<Button /> <Image /> ...
<Email>= email mode,<Page>= web,<Document>= print. They thread the mode to all children.- A
<Row>takeslayout={ColumnLayouts.TwoEqual}(orcells={[1,1]}) and must contain exactly the matching number of<Column>children. - Items (
Heading,Paragraph,Button,Image,Divider,Social,Menu,Table,Video,Html) go inside Columns, never directly in a Row.
Render
import { renderToHtml, renderToJson } from '@unlayer/react-elements';
const html = renderToHtml(<Email>…</Email>); // final HTML string (no hydration markers)
const json = renderToJson(<Email>…</Email>); // Unlayer design JSON (import into the editor)
Pass the root element (
<Email>/<Page>/<Document>) directly. If you wrap your template in your own component, call it —renderToJson(MyEmail())— don't pass<MyEmail />(the renderers read the root element type, so a wrapper component isn't recognized).
Complete example
import {
Email, Row, Column, ColumnLayouts,
Heading, Paragraph, Button,
} from '@unlayer/react-elements';
const sans = { label: 'Sans', value: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif" };
export function Welcome() {
return (
<Email backgroundColor="#F6F9FC" contentWidth="600px" fontFamily={sans} previewText="You're all set — get started inside.">
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#FFFFFF" padding="32px 40px 8px 40px">
<Column>
<Heading headingType="h4" fontSize="12px" fontWeight={700} color="#635BFF" letterSpacing="0.08em">
WELCOME
</Heading>
<Heading headingType="h1" fontSize="30px" fontWeight={700} color="#1A1F36" lineHeight="120%">
You're all set.
</Heading>
<Paragraph html="Everything's ready — here's how to get started." fontSize="16px" color="#697386" lineHeight="155%" />
</Column>
</Row>
<Row layout={ColumnLayouts.OneColumn} backgroundColor="#FFFFFF" padding="16px 40px 36px 40px">
<Column>
<Button href="https://example.com" backgroundColor="#635BFF" color="#FFFFFF"
fontSize="16px" fontWeight={600} padding="14px 28px" borderRadius="8px" textAlign="center">
Get started
</Button>
</Column>
</Row>
</Email>
);
}
Critical conventions — use canonical shapes
These shapes work in every version. Non-canonical forms (a string fontFamily, a bare-number fontSize) are not reliable.
- fontFamily is an object:
{ label, value }, e.g.{ label: 'Arial', value: 'arial, sans-serif' }. Define it once and reuse. - fontWeight is a number (
700). fontSize / padding / lineHeight / letterSpacing are strings with units:fontSize="28px",padding="20px 40px"(and"0px", never"0"),lineHeight="150%"(or unitless"1.5"),letterSpacing="0.08em". - Big numbers / amounts must be
<Heading>, never<Paragraph>— a paragraph's margin scales with font size and balloons the layout. - Column count must match the Row layout (
TwoEqual→ exactly 2<Column>), or the row renders empty. - Inline formatting goes in
<Paragraph html="…">with real tags (<b>,<a href>). Heading/Paragraph children must be a plain string — passing JSX with tags as children does not render correctly. Usehtmlfor anything formatted. previewTexton the root shows inbox preview text in the rendered email HTML.
Image sizing — match Unlayer's model
src is a URL string or { url, width?, height? }, where width/height are the image's NATURAL size, not a display width.
- Default is responsive — the image fills its column, capped at its natural size.
<Image src="…" />or<Image src={{ url, width: 400, height: 260 }} />both render responsively. Provide the naturalwidth/heightwhen you know them. - For a fixed display size, set it on the src object as
autoWidth:false+maxWidth(a percent of the container):<Image src={{ url: 'https://…', autoWidth: false, maxWidth: '50%' }} altText="…" /> - Heads-up: large / hero images currently render at up to ~500px wide and won't go fully edge-to-edge — center them on the background.
Making it actually look good
Agent-generated emails often look cheap. To make them genuinely nice:
-
Never use
<Table headers={…} data={…}>for a details list or line items. The shorthand renders a bordered, zero-padding spreadsheet. Build label/value rows instead — a muted label left, a bold value right, separated by a hairline using the Column'sborderobject:const HAIRLINE = { borderBottomWidth: '1px', borderBottomStyle: 'solid', borderBottomColor: '#EBEBEB' }; function detailRow(label: string, value: string, last = false) { const cell = { padding: '14px 0', border: last ? undefined : HAIRLINE }; return ( <Row layout={ColumnLayouts.TwoEqual} backgroundColor="#FFFFFF" padding="0 40px"> <Column {...cell}><Paragraph html={label} fontSize="14px" color="#717171" /></Column> <Column {...cell}><Paragraph html={`<b>${value}</b>`} fontSize="14px" color="#222222" textAlign="right" /></Column> </Row> ); } // call it inline: {detailRow('Total', '$1,248.00', true)} — NOT <detailRow/>; // a Row must be a direct child of the root, so a section helper must return a <Row>. -
Use the brand's real palette; put white content rows on a light-gray
Email backgroundColorfor a card feel. -
Add an eyebrow: a small uppercase
<Heading fontSize="12px" fontWeight={700} letterSpacing="0.08em">in an accent color above the headline. -
Generous, consistent 40–48px horizontal padding and intentional vertical rhythm. Solid brand CTA with
borderRadius~8–10px. -
Full-width button (when you want one) — pass the top-level
widthprop ("100%", or"200px"for a fixed width):<Button width="100%" href="…" backgroundColor="…" color="#FFFFFF" fontSize="16px" fontWeight={700} padding="14px 28px" borderRadius="8px" textAlign="center">Get started</Button>
Component quick reference
Email/Page/Document (backgroundColor, contentWidth, fontFamily, textColor, previewText) · Row (layout/cells, backgroundColor, padding) · Column (padding, backgroundColor, borderRadius, border) · Heading (headingType h1–h6 / level, text/string children, fontSize, fontWeight, color, lineHeight, letterSpacing, textAlign, fontFamily) · Paragraph (html or string children/text) · Button (href, backgroundColor, color, padding, borderRadius, textAlign; size via values for a fixed width) · Image (src string or object, altText, action) · Divider (borderTop*) · Social (icons={[{name,url}]}) · Menu (items={[{text,href}]}) · Table (headers/data — for real tabular data only) · Video (videoUrl) · Html (html).
See references/component-reference.md for full prop tables and references/patterns.md for ready-to-use section recipes.
Out of scope
This skill is the code-first library. For the drag-and-drop visual editor SDK, see unlayer-integration; for export/Cloud API, see unlayer-export.