Docs
Getting Started
Manual Install

Manual Install

MailingUI is a set of opinionated React components, built on top of React.email (opens in a new tab), designed to make the creation of emails easier. Before you continue, make sure to read Getting Started - Introduction.

Install dependencies

We need React.email (opens in a new tab) components as our baseline and their render utility function to transform your emails into HTML.

npm install @react-email/components @react-email/render

If you are planning to write your emails using MDX (opens in a new tab). You will also need:

npm install @mdx-js/react
⚠️

We assume you have an integration to compile MDX to JS, such as @mdx-js/esbuild, @mdx-js/loader, @mdx-js/node-loader, or @mdx-js/rollup in place. If you don't, please refer to MDX Packages (opens in a new tab).

Create your working sub-directories

Whether you call it MailingUI or not is up to you. We recommend setting up a working directories where your emails, their components, theming, utils and assets will be.

          • Set up themes and utils

            Emails just work differently, there is no good solution for theming. Our components are styled using a theme config (fancy way of calling our theme object) that you can modify to fit your branding needs for quick and easy customization.

            ℹ️

            We recommend creating an index file to manage all the exports from these files, specially if using path aliases. One for themes, one for utils.

            /themes/types.ts
            import * as React from "react";
             
            // ⚠️ Proceed with caution
             
            export type Colors<T extends string> = Partial<
              Record<T, React.CSSProperties["color"]>
            >;
             
            type StyleKey<T extends string> = keyof JSX.IntrinsicElements | T;
             
            export type Styles<T extends string> = Partial<
              Record<StyleKey<T>, React.CSSProperties>
            >;
             
            /themes/theme.ts
            import type { Colors, Styles } from "./types";
             
            // HELPERS
             
            const round = (num: number) =>
              num
                .toFixed(7)
                .replace(/(\.[0-9]+?)0+$/, "$1")
                .replace(/\.0$/, "");
             
            export const remToPx = (rem: number) => `${round(rem * 16)}px`;
             
            // TYPE DEFINITIONS
             
            /**
             * Color variants for MailingUI Components
             *
             * Not meant to be exported
             */
            type ColorVariants =
              | "global"
              | "muted"
              | "muted-background"
              | "primary"
              | "primary-foreground"
              | "destructive"
              | "destructive-foreground";
             
            export type ThemeColors<T extends string = never> = Colors<ColorVariants | T>;
             
            /**
             * Theme variants for MailingUI Components
             *
             * Not meant to be exported
             */
            type ThemeVariants =
              | "global"
              | "headings"
              | "text"
              | "muted"
              | "lead"
              | "small"
              | "block"
              | "compact"
              | "primary"
              | "secondary"
              | "destructive"
              | "rounded";
             
            export type Theme<T extends string = never> = Styles<ThemeVariants | T>;
             
            // COLORS
             
            /**
             * Themed colors for MailingUI Components
             *
             * Add any colors with CSS for consistency
             * in your styles object
             *
             * Not meant to be exported
             *
             */
            const colors: ThemeColors = {
              global: "#262626", //neutral-800
              muted: "#737373", // neutral-500
              "muted-background": "#f5f5f5", //neutral-100
              primary: "#171717", // neutral-900
              "primary-foreground": "#fafafa", // neutral-50
              destructive: "#b91c1c", // red-700
              "destructive-foreground": "#fef2f2", // red-50
            };
             
            // THEME
             
            /**
             * Theme for MailingUI Components
             *
             * Add any variants with CSS to access their
             * styles using the object's key or
             * as a utility class combined with `cx`
             *
             */
            export const theme: Theme = {
              global: {
                fontFamily: "system-ui, sans-serif",
                color: colors.global,
                marginBottom: `${remToPx(1.75)}`,
              },
              headings: {
                fontFamily: "system-ui, sans-serif",
                color: colors.global,
                letterSpacing: remToPx(-0.05),
                marginTop: `${remToPx(2.5)}`,
                fontWeight: 300,
              },
              text: {
                fontSize: remToPx(1.125),
                lineHeight: remToPx(2),
                fontWeight: 300,
              },
              h1: {
                fontSize: remToPx(3.75),
                lineHeight: remToPx(3.75),
                letterSpacing: remToPx(-0.05),
                marginTop: 0,
              },
              h2: {
                fontSize: remToPx(3),
                lineHeight: remToPx(3),
              },
              h3: {
                fontSize: remToPx(2.25),
                lineHeight: remToPx(2.5),
              },
              h4: {
                fontSize: remToPx(1.875),
                lineHeight: remToPx(2.25),
              },
              p: {
                marginTop: 0,
              },
              blockquote: {
                fontStyle: "italic",
                fontWeight: 500,
                marginLeft: `${remToPx(0)}`,
                marginRight: `${remToPx(0)}`,
                padding: `0 0 0 ${remToPx(1)}`,
                borderLeft: `${remToPx(0.25)} solid ${colors.muted}`,
              },
              hr: {
                marginTop: `${remToPx(2)}`,
                width: "100%",
                border: "none",
                borderTop: `${remToPx(0.1)} solid ${colors.muted}`,
              },
              code: {
                fontFamily:
                  "ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace",
                whiteSpace: "nowrap",
                borderRadius: `${remToPx(0.25)}`,
                padding: `${remToPx(0.125)} ${remToPx(0.25)}`,
                backgroundColor: colors["muted-background"],
                color: colors.muted,
              },
              a: {
                textDecoration: "underline",
                textUnderlineOffset: "6px",
                color: "inherit",
              },
              ul: {
                paddingLeft: `${remToPx(1.625)}`,
              },
              ol: {
                paddingLeft: `${remToPx(1.625)}`,
              },
              li: {
                paddingLeft: `${remToPx(0.375)}`,
                marginBottom: `${remToPx(0.625)}`,
              },
              figure: {
                margin: 0,
                width: "100%",
              },
              img: {
                maxWidth: "100%",
                display: "block",
                outline: "none",
                border: "none",
                textDecoration: "none",
              },
              figcaption: {
                paddingTop: `${remToPx(0.5)}`,
                textAlign: "center",
              },
              muted: {
                color: colors.muted,
              },
              primary: {
                border: `1px solid ${colors.primary}`,
                backgroundColor: colors.primary,
                color: colors["primary-foreground"],
              },
              secondary: {
                border: `1px solid ${colors.primary}`,
                backgroundColor: "transparent",
              },
              destructive: {
                border: `1px solid ${colors.destructive}`,
                backgroundColor: colors.destructive,
                color: colors["destructive-foreground"],
              },
              lead: {
                color: colors.muted,
                fontSize: remToPx(1.5),
              },
              small: {
                fontSize: remToPx(0.875),
                lineHeight: remToPx(1.5),
              },
              block: {
                display: "block",
                marginBottom: `${remToPx(1)}`,
              },
              compact: {
                marginTop: 0,
                marginBottom: 0,
              },
              rounded: {
                borderRadius: remToPx(1),
              },
            };
             
            /utils/utils.tsx
            import * as React from "react";
             
            import { Row, Column } from "@react-email/components";
             
            import { type MDXComponents } from "mdx/types";
             
            import { Badge } from "../components/badge/Badge";
            import { Button } from "../components/button/Button";
            import { Typography } from "../components/typography/Typography";
             
            import * as mailingUIComponents from "@mailingui/components";
            import { theme, type Theme } from "@mailingui/themes";
             
            export const cx = (
              inputStyles: (keyof Theme | React.CSSProperties | undefined | boolean)[],
              config: {
                theme?: Theme;
              } = { theme }
            ): React.CSSProperties =>
              inputStyles
                .filter((s): s is keyof Theme | React.CSSProperties => Boolean(s))
                .reduce<React.CSSProperties>((mergedStyles, style) => {
                  if (typeof style === "string") {
                    return { ...mergedStyles, ...theme[style], ...config.theme?.[style] };
                  }
                  return { ...mergedStyles, ...style };
                }, {});
             
            type Without<T, K> = Pick<T, Exclude<keyof T, K>>;
             
            type MUICompType = typeof mailingUIComponents;
             
            type ComponentProps<T> = T extends React.ComponentType<infer P> ? P : never;
             
            type ApplyThemeType = Partial<MUICompType>;
             
            export function applyTheme<T extends ApplyThemeType>(
              components: T,
              theme: Theme
            ): Without<T, "getMDXComponents" | "Grid" | "Cell"> {
              const themables = {};
             
              Object.entries(components).forEach(([key, comp]) => {
                if (typeof comp !== "function") return;
             
                const Comp = comp as React.ComponentType<{ theme: Theme }>;
             
                Object.assign(themables, {
                  [key]: (props: ComponentProps<typeof Comp>) => (
                    <Comp {...props} theme={theme} />
                  ),
                });
              });
             
              return themables as T;
            }
             
            export function getMDXComponents({
              components,
              theme,
              baseUrl,
            }: {
              components?: MDXComponents;
              theme: Theme;
              baseUrl?: string;
            }): MDXComponents {
              const defaultComponents: MDXComponents = {
                // HTML Mappings
                h1: (props) => <Typography.H1 theme={theme} {...props} />,
                h2: (props) => <Typography.H2 theme={theme} {...props} />,
                h3: (props) => <Typography.H3 theme={theme} {...props} />,
                h4: (props) => <Typography.H4 theme={theme} {...props} />,
                p: (props) => <Typography.P theme={theme} {...props} />,
                blockquote: (props) => <Typography.Blockquote theme={theme} {...props} />,
                hr: (props) => <Typography.HR theme={theme} {...props} />,
                code: (props) => <Typography.Code theme={theme} {...props} />,
                a: (props) => <Typography.Link theme={theme} {...props} />,
                ul: (props) => <Typography.UL theme={theme} {...props} />,
                ol: (props) => <Typography.OL theme={theme} {...props} />,
                li: (props) => <Typography.LI theme={theme} {...props} />,
                img: ({ title, src, ...props }) => (
                  <Typography.Img
                    theme={theme}
                    caption={title}
                    src={`${baseUrl ?? ""}${src}`}
                    {...props}
                  />
                ),
                // React Email Components
                Row: (props) => <Row {...props} />,
                Column: (props) => <Column {...props} />,
                // MailingUI Components
                H1: (props) => <Typography.H1 theme={theme} {...props} />,
                H2: (props) => <Typography.H2 theme={theme} {...props} />,
                H3: (props) => <Typography.H3 theme={theme} {...props} />,
                H4: (props) => <Typography.H4 theme={theme} {...props} />,
                P: (props) => <Typography.P theme={theme} {...props} />,
                Blockquote: (props) => <Typography.Blockquote theme={theme} {...props} />,
                HR: (props) => <Typography.HR theme={theme} {...props} />,
                Code: (props) => <Typography.Code theme={theme} {...props} />,
                Link: (props) => <Typography.Link theme={theme} {...props} />,
                UL: (props) => <Typography.UL theme={theme} {...props} />,
                OL: (props) => <Typography.OL theme={theme} {...props} />,
                LI: (props) => <Typography.LI theme={theme} {...props} />,
                Img: ({ title, src, ...props }) => (
                  <Typography.Img
                    theme={theme}
                    caption={title}
                    src={`${baseUrl ?? ""}${src}`}
                    {...props}
                  />
                ),
                Badge: (props) => <Badge theme={theme} {...props} />,
                Button: (props) => <Button theme={theme} {...props} />,
              };
              return { ...defaultComponents, ...components };
            }
             
            ⚠️

            This theme config is completely arbitrary but completely type safe, feel free to take aid in its types and modify it to fit your needs. Just make sure to check your components.

            Configure your path aliases (optional)

            Create path aliases to help you write email templates easier while using MailingUI

            {
              "compilerOptions": {
                {...}
                "baseUrl": ".",
                "paths": {
                  "@mailingui/components": ["./src/mailingui/components/index.ts"],
                  "@mailingui/themes": ["./src/mailingui/themes/index.ts"],
                  "@mailingui/utils": ["./src/mailingui/utils/index.ts"]
                }
              },
            }
            ℹ️

            Path alias depends on where you decide to install your MailingUI components. Example above shows installation in ./src/mailingui

            Congratulations! 🥳 Once you've install these dependencies you can copy and paste the components you need. See components for the full list.