Configuration
Three levels of configuration, from narrowest to broadest:
- Per-component props —
fontSize,backgroundColor,padding, and so on. - Per-render config — passed to
renderToHtml(or any other render function) as a second argument. - Provider-scoped config — wrap the tree in
<UnlayerProvider>(Client Components) or passconfigdirectly to<Body>(Server Components).
This page covers global config and the prop rules every component shares.
<UnlayerProvider> (Client Components)
Wrap your tree with <UnlayerProvider> to set values that apply to every nested component. The provider is "use client", so for Server Components see the config prop on <Body> below.
import {
UnlayerProvider,
Email,
Row,
Column,
ColumnLayouts,
Social,
Menu,
} from '@unlayer/react-elements';
function App() {
return (
<UnlayerProvider
config={{
cdnBaseUrl: 'https://my-cdn.example.com',
mergeTagState: { firstName: 'Jane', company: 'Acme' },
textDirection: 'ltr',
}}
>
<Email>
<Row layout={ColumnLayouts.OneColumn}>
<Column>
<Social
icons={[{ name: 'Facebook', url: 'https://facebook.com/acme' }]}
/>
<Menu
items={[
{ text: 'Home', href: '/' },
{ text: 'About', href: '/about' },
]}
/>
</Column>
</Row>
</Email>
</UnlayerProvider>
);
}
UnlayerProviderProps
interface UnlayerProviderProps {
config: Partial<UnlayerConfig>;
children: React.ReactNode;
}
Full UnlayerConfig shape
interface UnlayerConfig {
cdnBaseUrl: string;
mode?: 'web' | 'email' | 'document';
textDirection?: string;
mergeTagState?: Record<string, any>;
toSafeHtml?: (text: string, options?: any) => string;
headConfig?: {
hasFeature?: (featureName: string) => boolean;
getInitialValues?: (containerType: string) => Record<string, any>;
};
}
| Key | Type | Default | Purpose |
|---|---|---|---|
cdnBaseUrl | string | "https://cdn.tools.unlayer.com" | Base URL for asset resolution |
mode | 'web' | 'email' | 'document' | "web" | Default render mode (locked by <Email> / <Page> / <Document> wrappers) |
textDirection | string | — | Text direction for the whole tree (e.g., "ltr", "rtl") |
mergeTagState | Record<string, any> | — | Values used to substitute merge tags during render |
toSafeHtml | (text, options?) => string | — | Optional sanitizer hook applied to user-supplied HTML strings |
headConfig | { hasFeature?, getInitialValues? } | — | Advanced — feature flags and per-container initial-value overrides |
The defaults are exported as DEFAULT_CONFIG:
import { DEFAULT_CONFIG } from '@unlayer/react-elements';
One gotcha worth flagging: <UnlayerProvider> only delivers config to children through a root wrapper (<Email>, <Page>, <Document>, or <Body>). Components placed inside the provider but outside a wrapper won't see it. The provider needs the wrapper as the conduit.
useUnlayerConfig()
For custom client-side components that need to read the active config:
'use client';
import { useUnlayerConfig } from '@unlayer/react-elements';
function CdnImage({ path }: { path: string }) {
const { cdnBaseUrl } = useUnlayerConfig();
return <img src={`${cdnBaseUrl}/${path}`} />;
}
config Prop (Server Components)
<UnlayerProvider> uses React Context, which isn't available inside Server Components. For SSR, pass config directly to <Body>. The semantic wrappers (<Email>, <Page>, <Document>) forward it transparently:
import {
Body,
Row,
Column,
Paragraph,
renderToHtml,
} from '@unlayer/react-elements';
const html = renderToHtml(
<Body mode="email" config={{ cdnBaseUrl: 'https://cdn.example.com' }}>
<Row>
<Column>
<Paragraph>SSR-safe</Paragraph>
</Column>
</Row>
</Body>,
);
Passing config through the render function
renderToHtml, renderToHtmlParts, and renderToPlainText accept an optional second config argument:
const html = renderToHtml(<Email>{/* ... */}</Email>, {
cdnBaseUrl: 'https://cdn.example.com',
});
There's a composition gotcha worth knowing: the render function injects config by cloning the outermost element you pass. If that element is one of your own components (say <MyEmail />), the config lands on MyEmail, which silently drops it unless you forward it. The wrapper inside never sees the override.
Two ways to make config work cleanly:
// Option A — render the wrapper directly
renderToHtml(<Email>{/* ... */}</Email>, { cdnBaseUrl: '...' });
// Option B — forward config in your component
function MyEmail({ config }: { config?: Partial<UnlayerConfig> }) {
return <Email config={config}>{/* ... */}</Email>;
}
renderToHtml(<MyEmail />, { cdnBaseUrl: '...' });
See Rendering API for the full signatures.
Critical Prop Rules
These rules apply across every component. Following them keeps the renderer happy and the output predictable.
fontFamily is an object, not a string
// correct
<Heading fontFamily={{ label: 'Arial', value: 'arial,helvetica,sans-serif' }}>
Hello
</Heading>
// wrong
<Heading fontFamily="Arial">Hello</Heading>
fontWeight is a number, not a string
// correct
<Button fontWeight={700}>Click me</Button>
// wrong
<Button fontWeight="700">Click me</Button>
Use a wrapper as your root
Always put a <Row> inside <Email> / <Page> / <Document> / <Body>. The wrapper is what tells nested components which output mode to render for.
href accepts a plain string or a structured object
// shorthand — auto-wrapped
<Button href="https://example.com">Go</Button>
// expanded form — when you need a target
<Button href={{ name: 'web', values: { href: 'https://example.com', target: '_blank' } }}>
Open in new tab
</Button>
Image src accepts a plain string or a structured object
// shorthand — auto-wrapped to { url, autoWidth: true, maxWidth: '100%' }
<Image src="https://example.com/hero.jpg" />
// expanded form — when you need width control
<Image src={{ url: 'https://example.com/hero.jpg', width: 600, autoWidth: false, maxWidth: '100%' }} />
Image accepts alt as shorthand for altText
// equivalent
<Image src="..." alt="Hero banner" />
<Image src="..." altText="Hero banner" />
Heading uses headingType in typed code
// canonical
<Heading headingType="h2" fontSize="20px">Section</Heading>
// also works at runtime, but TypeScript rejects it
<Heading level="h2" fontSize="20px">Section</Heading>
level is a runtime alias for headingType. The public HeadingProps typing doesn't expose it, so strict TS will flag it.
Paragraph accepts children OR an html prop
// children — supports rich React
<Paragraph fontSize="14px">Hello <strong>world</strong></Paragraph>
// rich HTML string via the `html` prop (supports inline tags: <b>, <i>, <u>, <s>, <a>, <code>)
<Paragraph html="Hello <b>bold</b> and <a href='#'>link</a>" fontSize="14px" />
There's no text prop in the public typings for <Paragraph>. (The runtime accepts one for parity with <Heading> and <Button>, but TS will reject it.)
Padding values need a unit
// correct
<Row padding="0px" />
// wrong — bare numbers as strings are not allowed
<Row padding="0" />
Common Font Stacks
Three ready-to-use font configurations:
const sansFont = {
label: 'Sans Serif',
value: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
};
const serifFont = {
label: 'Georgia',
value: "Georgia, 'Times New Roman', Times, serif",
};
const monoFont = {
label: 'Monospace',
value: "'SF Mono', 'Fira Code', 'Roboto Mono', monospace",
};
<Heading fontFamily={sansFont}>Hello</Heading>;
Other Exports
| Export | Purpose |
|---|---|
DEFAULT_CONFIG | The default UnlayerConfig object |
useUnlayerConfig | Hook to read the active config in client components |
validateColumnLayout(layout, columnCount) | Throws if column count doesn't match the layout |
htmlToTextJson(html) | Convert an HTML string to Paragraph's textJson shape |
See Also
- Limitations — common mistakes and how to avoid them
- Components — Content — per-component prop reference
- Wrappers —
<Body>advanced usage