Unlayer elements
AI skills for integrating the Unlayer drag-and-drop editor. Install with: npx skills add unlayer/unlayer-skills
npx -y skills add unlayer/unlayer-skills --skill unlayer-elementsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 12 stars12 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build emails, pages, and documents in code with @unlayer/react-elements — React components that render to email-safe (table) HTML, responsive web HTML, or print/PDF. Use when generating templates programmatically without the visual editor.
SKILL.md
9.2 KB, as published. Nobody here has run it
Build with Unlayer Elements (code-first)
Overview
@unlayer/react-elements is a set of React components for building emails, pages, and documents in code — no visual editor. 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. 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) |
| 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.