Skip to main content

Wrappers

There are four root wrappers. Three are semantic shortcuts; the fourth is the underlying primitive.

WrapperOutput StyleTargetWhen to use
<Email>Table-based HTMLEmail clientsSending emails
<Page>div + flexbox HTMLResponsive webLanding pages, web previews
<Document>Print-tuned HTMLPDF generationInvoices, reports
<Body>Whatever mode saysAdvancedSet mode dynamically, or thread an SSR config prop explicitly

Email, Page, and Document are thin wrappers around Body that lock the mode prop. Roughly:

function Email(props) {
return <Body {...props} mode="email" />;
}
function Page(props) {
return <Body {...props} mode="web" />;
}
function Document(props) {
return <Body {...props} mode="document" />;
}

All three accept the same props as Body (minus mode).


<Email>

For content that will go through an email service provider and render inside an email client. The output is tables, inline styles, and the rest of the bag of tricks that survives Outlook and Gmail.

Props (selected)

EmailProps = Omit<BodyProps, "mode">. The most commonly used:

PropTypeDefault
backgroundColorstring"#F7F8F9"
contentWidthstring"500px"
contentAlign"center" | "left" | "right""center"
contentVerticalAlign"top" | "middle" | "bottom""middle"
fontFamily{ label: string, value: string }{ label: "Arial", value: "arial,helvetica,sans-serif" }
textColorstring"#000000"
linkStyleLinkStyle{ body: true, linkColor: "#0000ee", linkHoverColor: "#0000ee", linkUnderline: true, linkHoverUnderline: true }
backgroundImage{ url, fullWidth, repeat, size, position }empty image
previewTextstring
configPartial<UnlayerConfig>(SSR escape hatch)

previewText is the preheader most inboxes show next to the subject. It renders as hidden HTML padded to ~150 characters with invisible Unicode so the inbox preview doesn't bleed into the next visible content.

LinkStyle shape

interface LinkStyle {
body?: boolean; // true on the wrapper — applies styles to body links
inherit?: boolean; // true on a content component — inherit body link style
linkColor?: string;
linkHoverColor?: string;
linkUnderline?: boolean;
linkHoverUnderline?: boolean;
}

The wrapper uses body: true to mark its values as the document-level link style. Content components (Heading, Paragraph, Table, etc.) accept the same shape but use inherit: true to opt into the body-level style instead of defining their own.

backgroundImage shape

interface BackgroundImage {
url: string;
fullWidth?: boolean;
repeat?: 'no-repeat' | 'repeat' | 'repeat-x' | 'repeat-y';
size?: 'cover' | 'contain' | 'custom';
position?: 'center' | 'top' | 'bottom' | 'left' | 'right' | string;
}

Set url: "" (the default) for no background image.

Example

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

const html = renderToHtml(
<Email
backgroundColor="#f4f4f4"
contentWidth="600px"
previewText="Your weekly digest is here"
>
<Row layout={ColumnLayouts.OneColumn}>
<Column padding="20px">
<Paragraph fontSize="16px">Hello from Unlayer!</Paragraph>
</Column>
</Row>
</Email>,
);

<Page>

For responsive landing pages, web previews, or any HTML you'll render in a browser viewport. Uses div + flexbox instead of tables.

Props

PageProps = Omit<BodyProps, "mode">. Same shape as <Email>. previewText is meaningful only in email mode and is ignored here.

Example

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

const html = renderToHtml(
<Page contentWidth="1200px" backgroundColor="#ffffff">
<Row layout={ColumnLayouts.TwoEqual} padding="40px">
<Column>
<Heading headingType="h1" fontSize="32px">
Build with Unlayer
</Heading>
</Column>
<Column>
<Paragraph fontSize="16px">
Render the same components to email, web, and PDF.
</Paragraph>
</Column>
</Row>
</Page>,
);

<Document>

For print-targeted output: invoices, certificates, reports, contracts. Pair the HTML output with a headless browser or PDF library on your server to produce the final PDF.

Props

DocumentProps = Omit<BodyProps, "mode">. Same shape as <Email> and <Page>.

Example

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

const html = renderToHtml(
<Document contentWidth="8.5in">
<Row layout={ColumnLayouts.OneColumn} padding="40px">
<Column>
<Heading headingType="h1" fontSize="28px">
Invoice #1042
</Heading>
<Paragraph fontSize="14px">Issued on 2026-05-05</Paragraph>
</Column>
</Row>
</Document>,
);

// hand the html string to puppeteer / playwright / wkhtmltopdf etc.

<Body> (Advanced)

<Body> is the primitive the others wrap. Use it directly when you need to:

  • Set mode dynamically (e.g., from a runtime flag).
  • Pass an SSR config prop instead of relying on <UnlayerProvider> Context.

Props

PropTypeNotes
mode"web" | "email" | "document"Default: "web" (or whatever config.mode says)
configPartial<UnlayerConfig>For Server Components where Context isn't available
previewTextstringEmail-mode preview text
childrenReact.ReactNodeMust be <Row> elements
...semantic props(same as Email/Page/Document)backgroundColor, contentWidth, etc.

Example: SSR with explicit config

import {
Body,
Row,
Column,
Paragraph,
renderToHtml,
} from '@unlayer/react-elements';

const html = renderToHtml(
<Body
mode="email"
config={{ cdnBaseUrl: 'https://cdn.example.com' }}
backgroundColor="#ffffff"
previewText="Preview from a Server Component"
>
<Row>
<Column>
<Paragraph>Hello from a Server Component</Paragraph>
</Column>
</Row>
</Body>,
);

For a fixed render mode, the semantic wrappers (<Email>, <Page>, <Document>) read better.


Choosing a Wrapper

The wrapper is decided by where the HTML will be viewed, not by what it contains. A "newsletter" rendered for the web is a <Page>; the same content mailed out is an <Email>.

You can render the same content tree under different wrappers in the same codebase, which is useful when you need both an email and a web archive of the same campaign.


See Also