
React Email
- 81 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with frontend development tasks during AI-assisted development.
About
react-email is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted coding.
- react-email
- Frontend Development
- AI-coding skill
React Email by the numbers
- 81 all-time installs (skills.sh)
- +5 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,104 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill react-emailAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 81 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with frontend development tasks during AI-assisted development.
Files
React Email
Overview
React Email is a library for building responsive HTML emails using React components. It provides a set of unstyled, accessible components that render to email-client-compatible HTML. Supports inline styles, Tailwind CSS via a wrapper component, custom web fonts, and rendering to both HTML and plain text.
When to use: Transactional emails (welcome, password reset, receipts), marketing templates, email design systems, any project that renders emails server-side.
When NOT to use: Static HTML email templates with no dynamic content, projects that already use a dedicated email builder (MJML, Maizzle), or when you only need plain-text emails.
Quick Reference
| Pattern | API | Key Points |
|---|---|---|
| Document structure | Html, Head, Body | Html wraps everything, Head loads fonts/meta |
| Content container | Container | Centers content, sets max-width |
| Layout grid | Section, Row, Column | Table-based layout for email clients |
| Text content | Text, Heading | Heading accepts as prop for h1-h6 |
| Links | Link | Standard anchor, href required |
| Call-to-action | Button | Renders as link styled as button, href required |
| Images | Img | Always set width, height, alt |
| Divider | Hr | Horizontal rule between sections |
| Inbox preview | Preview | Sets preview text shown in inbox list |
| Custom fonts | Font | Place inside Head, set webFont and fallbackFontFamily |
| Tailwind styling | Tailwind | Wraps email, inlines Tailwind classes at render |
| Render to HTML | render(<Email />) | Async, returns HTML string |
| Render plain text | render(<Email />, { plainText: true }) | Strips HTML, returns text |
| Pretty output | render(<Email />, { pretty: true }) | Formatted HTML for debugging |
| Preview server | email dev | Local dev server with hot reload |
| Markdown content | Markdown | Renders Markdown as email-compatible HTML |
| Code blocks | CodeBlock | Syntax-highlighted code with theme support |
| Inline code | CodeInline | Monospace inline code spans |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
Using className with inline styles | Use either Tailwind (className) or inline style objects, not both on the same element |
Forgetting width/height on Img | Always specify dimensions to prevent layout shifts in email clients |
Nesting Container inside Container | Use one Container for centering, Section/Row/Column for inner layout |
| Using CSS Grid or Flexbox | Use Section, Row, Column for table-based layouts that work across email clients |
Placing Font outside Head | Font must be a child of Head to load correctly |
Using render() synchronously | render() is async, always await the result |
Wrapping Tailwind inside Html | Tailwind wraps Html, not the other way around |
Omitting Preview component | Without Preview, email clients show the first body text as preview |
| Using external CSS stylesheets | Email clients strip <link> tags; use inline styles or Tailwind component |
Passing React component to resend.emails.send | Render to HTML string first, then pass html to the send method |
Delegation
- Email template discovery: Use
Exploreagent - Email styling review: Use
Taskagent - Code review: Delegate to
code-revieweragent
If the resend skill is available, delegate email delivery tasks to it.Otherwise, recommend: npx skills add oakoss/agent-skills --skill resendReferences
- Built-in components: structure, content, and layout
- Styling with inline CSS, Tailwind, responsive patterns, and fonts
- Rendering to HTML, preview server, and provider integration
Components
All components are imported from @react-email/components.
import {
Html,
Head,
Body,
Container,
Section,
Row,
Column,
Text,
Heading,
Link,
Button,
Img,
Hr,
Preview,
Font,
Tailwind,
} from '@react-email/components';Document Structure
Every email starts with Html, Head, and Body. These map to the root HTML document structure.
import { Html, Head, Body, Container, Text } from '@react-email/components';
export const BasicEmail = () => {
return (
<Html lang="en" dir="ltr">
<Head />
<Body style={{ backgroundColor: '#ffffff', fontFamily: 'sans-serif' }}>
<Container style={{ maxWidth: '600px', margin: '0 auto' }}>
<Text>Hello from React Email</Text>
</Container>
</Body>
</Html>
);
};Html
Root element. Accepts lang and dir props for internationalization.
Head
Place inside Html before Body. Used to load Font components, meta tags, and the title element. Renders as an empty self-closing tag when no children are needed.
Body
Wraps all visible email content. Apply global background color and font-family here.
Container
Centers content horizontally and constrains width. Use one Container per email. Set maxWidth via style (typically 600px for email).
Content Components
Text
Renders a <p> tag. Supports margin shorthand props (m, mx, my, mt, mr, mb, ml).
import { Text } from '@react-email/components';
const Email = () => {
return (
<Text style={{ fontSize: '16px', lineHeight: '24px', color: '#333333' }}>
Thank you for your purchase. Your order is being processed.
</Text>
);
};Heading
Renders semantic heading elements (h1-h6). Use the as prop to set the level.
import { Heading } from '@react-email/components';
const Email = () => {
return (
<>
<Heading as="h1" style={{ fontSize: '24px', fontWeight: '600' }}>
Welcome
</Heading>
<Heading as="h2" mt="16px" mb="8px">
Your Account
</Heading>
</>
);
};Heading supports margin shorthand props: m, mx, my, mt, mr, mb, ml.
Link
Renders an anchor tag. Always set href. Use target="_blank" for external links.
import { Link } from '@react-email/components';
const Email = () => {
return (
<Link
href="https://example.com/dashboard"
style={{ color: '#5469d4', textDecoration: 'underline' }}
>
View Dashboard
</Link>
);
};Button
Renders a link styled as a button. Requires href. Renders using a table-based structure for maximum email client compatibility.
import { Button } from '@react-email/components';
const Email = () => {
return (
<Button
href="https://example.com/verify?token=abc123"
style={{
backgroundColor: '#5469d4',
color: '#ffffff',
padding: '12px 24px',
borderRadius: '4px',
textDecoration: 'none',
fontWeight: '600',
display: 'inline-block',
}}
>
Verify Email Address
</Button>
);
};Img
Renders an image. Always specify width, height, and alt to prevent layout shifts and improve accessibility.
import { Img } from '@react-email/components';
const Email = () => {
return (
<Img
src="https://example.com/logo.png"
width="150"
height="40"
alt="Company Logo"
style={{ margin: '0 auto', display: 'block' }}
/>
);
};Use absolute URLs for src in production. Relative paths only work in the preview server.
Hr
Renders a horizontal rule. Style with borderColor and margin.
import { Hr } from '@react-email/components';
const Email = () => {
return <Hr style={{ borderColor: '#e6e6e6', margin: '32px 0' }} />;
};Preview
Sets the preview text shown in email client inbox lists (next to the subject line). Place as a direct child of Html, before Body.
import { Html, Preview, Body, Text } from '@react-email/components';
const Email = () => {
return (
<Html>
<Preview>Your order #12345 has shipped and is on its way</Preview>
<Body>
<Text>Order details below...</Text>
</Body>
</Html>
);
};Without a Preview component, email clients display the first visible body text as the preview.
Layout Components
Email clients do not support CSS Grid or Flexbox reliably. React Email provides table-based layout components.
Section
Groups related content. Renders as a <table> element internally.
import { Section, Text } from '@react-email/components';
const Email = () => {
return (
<Section style={{ padding: '24px', backgroundColor: '#f9f9f9' }}>
<Text>This section has a background color and padding.</Text>
</Section>
);
};Row and Column
Create multi-column layouts. Row renders as a table row, Column as a table cell. Always use inside a Section.
import { Section, Row, Column, Img, Text } from '@react-email/components';
const Email = () => {
return (
<Section>
<Row>
<Column style={{ width: '50%', verticalAlign: 'top' }}>
<Img
src="https://example.com/product.png"
width="200"
height="200"
alt="Product"
/>
</Column>
<Column
style={{ width: '50%', verticalAlign: 'top', paddingLeft: '16px' }}
>
<Text style={{ fontWeight: 'bold', fontSize: '18px' }}>
Product Name
</Text>
<Text style={{ color: '#666666' }}>$29.99</Text>
</Column>
</Row>
</Section>
);
};Three-Column Layout
import { Section, Row, Column, Text } from '@react-email/components';
const Email = () => {
return (
<Section>
<Row>
<Column
style={{ width: '33.33%', verticalAlign: 'top', textAlign: 'center' }}
>
<Text style={{ fontWeight: 'bold' }}>Fast Delivery</Text>
<Text style={{ fontSize: '14px' }}>Get your order in 2 days</Text>
</Column>
<Column
style={{ width: '33.33%', verticalAlign: 'top', textAlign: 'center' }}
>
<Text style={{ fontWeight: 'bold' }}>Free Returns</Text>
<Text style={{ fontSize: '14px' }}>30-day return policy</Text>
</Column>
<Column
style={{ width: '33.33%', verticalAlign: 'top', textAlign: 'center' }}
>
<Text style={{ fontWeight: 'bold' }}>24/7 Support</Text>
<Text style={{ fontSize: '14px' }}>We are here to help</Text>
</Column>
</Row>
</Section>
);
};Markdown Component
Renders Markdown content as email-compatible HTML with customizable styles.
import { Markdown, Html } from '@react-email/components';
const Email = () => {
return (
<Html lang="en" dir="ltr">
<Markdown
markdownCustomStyles={{
h1: { color: 'red' },
h2: { color: 'blue' },
codeInline: { background: 'grey' },
}}
markdownContainerStyles={{
padding: '12px',
border: 'solid 1px black',
}}
>
{`# Hello, World!
This is **bold** and this is _italic_.`}
</Markdown>
</Html>
);
};CodeBlock Component
Renders code with syntax highlighting. Supports multiple themes and languages.
import { CodeBlock, dracula } from '@react-email/code-block';
const Email = () => {
const code = `export default async (req, res) => {
const html = await render(EmailTemplate({ firstName: 'John' }));
return Response.json({ html });
}`;
return (
<CodeBlock code={code} lineNumbers theme={dracula} language="javascript" />
);
};CodeInline Component
Renders inline code spans for email-safe monospace formatting.
import { CodeInline } from '@react-email/code-inline';
const Email = () => {
return (
<Text>
Run <CodeInline>npm install @react-email/components</CodeInline> to get
started.
</Text>
);
};Full Template Example
import {
Html,
Head,
Preview,
Body,
Container,
Heading,
Text,
Button,
Hr,
Img,
Section,
} from '@react-email/components';
interface WelcomeEmailProps {
name: string;
}
export const WelcomeEmail = ({ name }: WelcomeEmailProps) => {
return (
<Html>
<Head />
<Preview>Welcome to our platform, {name}</Preview>
<Body style={main}>
<Container style={container}>
<Img
src="https://example.com/logo.png"
width="150"
height="40"
alt="Company Logo"
style={{ margin: '0 auto 32px', display: 'block' }}
/>
<Heading
style={{ fontSize: '24px', fontWeight: '600', color: '#333' }}
>
Welcome, {name}
</Heading>
<Text style={{ fontSize: '16px', lineHeight: '24px', color: '#555' }}>
Thank you for joining. Get started by setting up your profile.
</Text>
<Section style={{ textAlign: 'center', margin: '32px 0' }}>
<Button
href="https://example.com/onboarding"
style={{
backgroundColor: '#5469d4',
color: '#ffffff',
padding: '12px 24px',
borderRadius: '4px',
textDecoration: 'none',
fontWeight: '600',
}}
>
Get Started
</Button>
</Section>
<Hr style={{ borderColor: '#e6e6e6', margin: '32px 0' }} />
<Text style={{ fontSize: '12px', color: '#999' }}>
Questions? Contact us at support@example.com
</Text>
</Container>
</Body>
</Html>
);
};
const main: React.CSSProperties = {
backgroundColor: '#f6f6f6',
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
};
const container: React.CSSProperties = {
maxWidth: '600px',
margin: '0 auto',
padding: '48px 20px',
backgroundColor: '#ffffff',
};
export default WelcomeEmail;Rendering
The render Function
The render function converts React Email components into HTML strings. It is async and must be awaited.
import { render } from '@react-email/components';Basic Rendering
import { render } from '@react-email/components';
import { WelcomeEmail } from './emails/welcome';
const html = await render(<WelcomeEmail name="Sarah" />);The output is a complete HTML document starting with <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"....
Render Options
import { render } from '@react-email/components';
import { WelcomeEmail } from './emails/welcome';
const html = await render(<WelcomeEmail name="Sarah" />);
const prettyHtml = await render(<WelcomeEmail name="Sarah" />, {
pretty: true,
});
const plainText = await render(<WelcomeEmail name="Sarah" />, {
plainText: true,
});
const plainTextFormatted = await render(<WelcomeEmail name="Sarah" />, {
plainText: true,
htmlToTextOptions: {
wordwrap: 80,
},
});| Option | Type | Description |
|---|---|---|
pretty | boolean | Format HTML with indentation (uses Prettier internally) |
plainText | boolean | Strip HTML and return plain text |
htmlToTextOptions | object | Options passed to html-to-text when plainText is true |
Error Handling
Always wrap render in try/catch for production use.
import { render } from '@react-email/components';
import { OrderEmail } from './emails/order';
async function renderEmail(orderId: string) {
try {
const html = await render(<OrderEmail orderId={orderId} />);
return html;
} catch (error) {
console.error('Failed to render email:', error);
throw error;
}
}Preview Server
React Email includes a local development server for previewing emails with hot reload.
Setup
npm install react-email -DAdd a script to package.json:
{
"scripts": {
"email:dev": "email dev"
}
}Project Structure
The dev server looks for email templates in a specific directory.
project/
├── emails/
│ ├── welcome.tsx
│ ├── order-confirmation.tsx
│ └── password-reset.tsx
├── package.json
└── ...Running the Preview
npx email devThis starts a local server (default http://localhost:3000) that renders each email template with hot reload. Changes to email components are reflected immediately.
Custom Source Directory
npx email dev --dir ./src/emails --port 3001| Flag | Description |
|---|---|
--dir | Directory containing email templates (default: ./emails) |
--port | Port number for the dev server (default: 3000) |
Exporting Static HTML
Generate static HTML files from email templates.
npx email export --outDir ./outIntegration with Email Providers
The pattern is always the same: render the React component to HTML, then pass the HTML string to your provider's send method.
Resend
import { render } from '@react-email/components';
import { Resend } from 'resend';
import { WelcomeEmail } from './emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
const emailHtml = await render(<WelcomeEmail name="Alex" />);
const { data, error } = await resend.emails.send({
from: 'onboarding@example.com',
to: 'user@example.com',
subject: 'Welcome to Our Platform',
html: emailHtml,
});
if (error) {
console.error('Resend error:', error);
}Resend also supports passing the React component directly:
import { Resend } from 'resend';
import { WelcomeEmail } from './emails/welcome';
const resend = new Resend(process.env.RESEND_API_KEY);
const { data, error } = await resend.emails.send({
from: 'onboarding@example.com',
to: 'user@example.com',
subject: 'Welcome to Our Platform',
react: WelcomeEmail({ name: 'Alex' }),
});When using the react prop, Resend calls render internally. This is a Resend-specific feature and does not work with other providers.
Nodemailer
import { render } from '@react-email/components';
import nodemailer from 'nodemailer';
import { InvoiceEmail } from './emails/invoice';
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 465,
secure: true,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
const emailHtml = await render(
<InvoiceEmail invoiceId="INV-001" amount={99.99} />
);
const plainText = await render(
<InvoiceEmail invoiceId="INV-001" amount={99.99} />,
{ plainText: true }
);
await transporter.sendMail({
from: 'billing@example.com',
to: 'customer@example.com',
subject: 'Invoice #INV-001',
html: emailHtml,
text: plainText,
});Include both html and text for maximum deliverability. Email clients that cannot render HTML will fall back to the plain text version.
SendGrid
import { render } from '@react-email/components';
import sendgrid from '@sendgrid/mail';
import { ShippingEmail } from './emails/shipping';
sendgrid.setApiKey(process.env.SENDGRID_API_KEY || '');
const emailHtml = await render(
<ShippingEmail orderId="12345" trackingUrl="https://example.com/track/abc" />
);
const plainText = await render(
<ShippingEmail orderId="12345" trackingUrl="https://example.com/track/abc" />,
{ plainText: true }
);
await sendgrid.send({
from: 'shipping@example.com',
to: 'customer@example.com',
subject: 'Your Order Has Shipped',
html: emailHtml,
text: plainText,
});AWS SES
import { render } from '@react-email/components';
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
import { NotificationEmail } from './emails/notification';
const ses = new SESClient({ region: 'us-east-1' });
const emailHtml = await render(<NotificationEmail message="Your report is ready" />);
const plainText = await render(
<NotificationEmail message="Your report is ready" />,
{ plainText: true }
);
await ses.send(
new SendEmailCommand({
Source: 'notifications@example.com',
Destination: {
ToAddresses: ['user@example.com'],
},
Message: {
Subject: { Data: 'New Notification' },
Body: {
Html: { Data: emailHtml },
Text: { Data: plainText },
},
},
})
);Email Template Pattern
Structure email templates as React components with typed props and a default export.
import {
Html,
Head,
Preview,
Body,
Container,
Text,
} from '@react-email/components';
interface PasswordResetProps {
resetUrl: string;
expiresInHours?: number;
}
export const PasswordResetEmail = ({
resetUrl,
expiresInHours = 24,
}: PasswordResetProps) => {
return (
<Html>
<Head />
<Preview>Reset your password</Preview>
<Body style={{ backgroundColor: '#f6f6f6', fontFamily: 'sans-serif' }}>
<Container
style={{ maxWidth: '600px', margin: '0 auto', padding: '48px 20px' }}
>
<Text>Click the link below to reset your password.</Text>
<Text>This link expires in {expiresInHours} hours.</Text>
</Container>
</Body>
</Html>
);
};
PasswordResetEmail.PreviewProps = {
resetUrl: 'https://example.com/reset?token=preview-token',
expiresInHours: 24,
} satisfies PasswordResetProps;
export default PasswordResetEmail;The PreviewProps static property provides default props for the preview server, making it easy to view the email during development.
Styling and Layout
Inline Styles
Email clients strip <style> tags and ignore external stylesheets. Inline styles are the most reliable approach.
Style Objects
Define reusable style objects with React.CSSProperties for type safety.
import { Html, Body, Container, Text, Heading } from '@react-email/components';
export const StyledEmail = () => {
return (
<Html>
<Body style={main}>
<Container style={container}>
<Heading style={heading}>Order Confirmed</Heading>
<Text style={text}>Your order has been placed successfully.</Text>
</Container>
</Body>
</Html>
);
};
const main: React.CSSProperties = {
backgroundColor: '#f6f6f6',
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
};
const container: React.CSSProperties = {
maxWidth: '600px',
margin: '0 auto',
padding: '48px 20px',
backgroundColor: '#ffffff',
borderRadius: '8px',
};
const heading: React.CSSProperties = {
fontSize: '24px',
fontWeight: '600',
color: '#111111',
margin: '0 0 16px',
};
const text: React.CSSProperties = {
fontSize: '16px',
lineHeight: '24px',
color: '#555555',
margin: '0 0 24px',
};Email-Safe CSS Properties
Not all CSS properties work across email clients. Stick to well-supported properties.
| Supported | Avoid |
|---|---|
background-color | background shorthand with gradients |
color, font-size, font-family | gap, grid, flex |
padding, margin | position: absolute/fixed |
border, border-radius | box-shadow (limited support) |
text-align, vertical-align | float (inconsistent) |
width, max-width, height | calc() (limited) |
display: block/inline-block | display: flex/grid |
table-layout | overflow |
Font Stack
Use a web-safe font stack as the base. Custom fonts are additive.
const fontStack =
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif";Tailwind CSS
The Tailwind component enables utility classes by inlining Tailwind styles at render time. It wraps the Html component.
Basic Tailwind Usage
import {
Html,
Body,
Container,
Text,
Heading,
Tailwind,
} from '@react-email/components';
export const TailwindEmail = () => {
return (
<Tailwind>
<Html>
<Body className="bg-gray-100 font-sans">
<Container className="mx-auto my-8 max-w-xl bg-white p-8 rounded-lg">
<Heading className="text-2xl font-bold text-gray-900 mb-4">
Welcome
</Heading>
<Text className="text-base text-gray-600 leading-relaxed">
Thanks for signing up.
</Text>
</Container>
</Body>
</Html>
</Tailwind>
);
};Custom Tailwind Config
Pass a config prop to extend Tailwind with custom colors, spacing, or fonts.
import {
Html,
Body,
Container,
Button,
Text,
Tailwind,
} from '@react-email/components';
export const BrandedEmail = () => {
return (
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: '#5469d4',
'brand-dark': '#3451b2',
},
spacing: {
'18': '4.5rem',
},
},
},
}}
>
<Html>
<Body className="bg-gray-50 font-sans">
<Container className="mx-auto my-8 p-8 bg-white rounded-lg max-w-2xl">
<Text className="text-base text-gray-700 mb-6">
Click below to get started.
</Text>
<Button
href="https://example.com"
className="bg-brand text-white font-semibold py-3 px-6 rounded-md no-underline"
>
Get Started
</Button>
</Container>
</Body>
</Html>
</Tailwind>
);
};Tailwind vs Inline Styles
Do not mix className and style on the same element. Pick one approach per component.
// Correct: Tailwind only
<Text className="text-base text-gray-700 mb-4">Content</Text>
// Correct: Inline only
<Text style={{ fontSize: '16px', color: '#555', marginBottom: '16px' }}>Content</Text>
// Avoid: Mixed on same element
<Text className="text-base" style={{ color: '#555' }}>Content</Text>Custom Fonts
Use the Font component inside Head to load web fonts. Always provide a fallbackFontFamily.
import { Html, Head, Body, Font, Text } from '@react-email/components';
export const CustomFontEmail = () => {
return (
<Html>
<Head>
<Font
fontFamily="Inter"
fallbackFontFamily="Helvetica"
webFont={{
url: 'https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2',
format: 'woff2',
}}
fontWeight={400}
fontStyle="normal"
/>
<Font
fontFamily="Inter"
fallbackFontFamily="Helvetica"
webFont={{
url: 'https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuBWYAZ9hiA.woff2',
format: 'woff2',
}}
fontWeight={700}
fontStyle="normal"
/>
</Head>
<Body style={{ fontFamily: 'Inter, Helvetica, Arial, sans-serif' }}>
<Text style={{ fontWeight: 400 }}>Regular weight text</Text>
<Text style={{ fontWeight: 700 }}>Bold weight text</Text>
</Body>
</Html>
);
};Font Props
| Prop | Type | Required | Description |
|---|---|---|---|
fontFamily | string | Yes | Font name to register |
fallbackFontFamily | string | Yes | Web-safe fallback (e.g., Verdana, Georgia, Helvetica) |
webFont | { url, format } | No | URL and format (woff2, woff) of the web font file |
fontWeight | number | No | Font weight (e.g., 400, 700) |
fontStyle | string | No | Font style (normal, italic) |
Load one Font component per weight/style combination.
Responsive Patterns
Email clients have limited support for @media queries. Use these patterns for responsive layouts.
Fluid Width
Use percentage widths with a max-width constraint.
import { Container, Section, Row, Column, Text } from '@react-email/components';
const Email = () => {
return (
<Container style={{ maxWidth: '600px', width: '100%', margin: '0 auto' }}>
<Section>
<Row>
<Column style={{ width: '50%', paddingRight: '8px' }}>
<Text>Left column</Text>
</Column>
<Column style={{ width: '50%', paddingLeft: '8px' }}>
<Text>Right column</Text>
</Column>
</Row>
</Section>
</Container>
);
};Single Column for Mobile
For critical content, use a single-column layout that works on all screen sizes.
import { Container, Text, Button } from '@react-email/components';
const Email = () => {
return (
<Container
style={{
maxWidth: '480px',
width: '100%',
margin: '0 auto',
padding: '0 16px',
}}
>
<Text style={{ fontSize: '16px', lineHeight: '24px' }}>
A narrower container naturally reads well on mobile without media
queries.
</Text>
<Button
href="https://example.com"
style={{
display: 'block',
width: '100%',
textAlign: 'center',
backgroundColor: '#5469d4',
color: '#ffffff',
padding: '12px',
borderRadius: '4px',
textDecoration: 'none',
}}
>
Full Width Button
</Button>
</Container>
);
};Dark Mode
Email client dark mode support varies. Use these strategies for consistent appearance.
Meta Tag Approach
Add a color-scheme meta tag in Head to indicate dark mode support.
import { Html, Head, Body, Container, Text } from '@react-email/components';
export const DarkModeEmail = () => {
return (
<Html>
<Head>
<meta name="color-scheme" content="light dark" />
<meta name="supported-color-schemes" content="light dark" />
</Head>
<Body style={{ backgroundColor: '#ffffff', color: '#111111' }}>
<Container>
<Text>This email supports dark mode detection.</Text>
</Container>
</Body>
</Html>
);
};Dark Mode Safe Colors
Choose colors that remain legible in both light and dark modes. Avoid pure white (#ffffff) backgrounds for inner containers and pure black (#000000) text when possible.
| Element | Light Mode | Dark Mode Safe |
|---|---|---|
| Background | #ffffff | #f6f6f6 (slightly off-white) |
| Text | #000000 | #111111 or #333333 |
| Links | #0066cc | #5469d4 (higher contrast blue) |
| Borders | #e6e6e6 | #cccccc |
Tailwind Dark Mode
The Tailwind component supports dark: variants that target email clients with dark mode.
import { Html, Body, Text, Tailwind } from '@react-email/components';
export const DarkModeTailwind = () => {
return (
<Tailwind>
<Html>
<Body className="bg-white dark:bg-gray-900">
<Text className="text-gray-900 dark:text-gray-100">
Adapts to dark mode in supporting email clients.
</Text>
</Body>
</Html>
</Tailwind>
);
};Not all email clients support dark: variants. Always ensure your base (light) styles are readable.