@kleb/templating (0.3.0)
Installation
@kleb:registry=npm install @kleb/templating@0.3.0"@kleb/templating": "0.3.0"About this package
@kleb/templating
Email-focused React template rendering.
import { Body, Head, Html, Tailwind, Text } from "@react-email/components";
import { createEmailTailwindConfig, renderEmailTemplate } from "@kleb/templating";
const theme = createEmailTailwindConfig({
colors: {
background: "#ffffff",
foreground: "#111827",
primary: "#2563eb",
primaryForeground: "#ffffff",
},
});
const WelcomeEmail = ({ name }: { name: string }) => (
<Html>
<Tailwind config={theme}>
<Head />
<Body className="bg-background text-foreground">
<Text>Hello {name}</Text>
</Body>
</Tailwind>
</Html>
);
const rendered = await renderEmailTemplate(WelcomeEmail, { name: "Ada" });
Theme values should be resolved colors. Do not pass CSS variables directly to
email templates. React Email's pixel-based Tailwind preset is included by
default; pass { pixelBasedPreset: false } to disable it.
Use placeholder rendering for subjects, text bodies, or small snippets:
import { renderHtmlTemplateString, renderTemplateString } from "@kleb/templating";
const subject = renderTemplateString("Welcome, {{user.name}}", {
user: { name: "Ada" },
});
const html = renderHtmlTemplateString("<p>Hello {{user.name}}</p>", {
user: { name: "<Ada>" },
});
Missing placeholders throw by default. Pass { missing: "keep" } or
{ missing: "empty" } when that behavior is explicitly desired.
Use the static registry for a known set of reusable email templates. It preserves
literal IDs and derives the props accepted by render and returned by get from
the selected ID:
import { createEmailTemplateRegistry, defineEmailTemplate } from "@kleb/templating";
const registry = createEmailTemplateRegistry([
defineEmailTemplate({
id: "welcome",
component: WelcomeEmail,
subject: "Welcome {{name}}",
preview: ({ name }) => `Hello ${name}`,
sampleProps: { name: "Ada" },
}),
]);
const rendered = await registry.render("welcome", { name: "Ada" });
const sample = await registry.renderSample("welcome");
Duplicate IDs are rejected when a registry is created. sampleProps is optional;
renderSample throws if the selected definition does not provide it. Static
validateProps remains available for additional runtime checks. The old
caller-selected registry.render<Props>(...) and registry.get<Props>(...)
overloads are deprecated but retained for one compatibility release. New code
should omit those type arguments so the ID controls the props type. Let
defineEmailTemplate infer props from the component, as above, to preserve the
literal ID. Existing defineEmailTemplate<Props>(...) calls remain accepted but
have a broad string ID; specify both generic arguments or remove the explicit
one when migrating to strict ID checks.
Use the separately named dynamic registry when IDs or payloads cross a dynamic
boundary. Its render method accepts unknown, and every definition must parse
or validate that value before rendering:
import { createDynamicEmailTemplateRegistry, defineDynamicEmailTemplate } from "@kleb/templating";
const dynamicRegistry = createDynamicEmailTemplateRegistry([
defineDynamicEmailTemplate({
id: "welcome",
component: WelcomeEmail,
subject: "Welcome {{name}}",
parseProps: (value: unknown): { name: string } => {
if (
typeof value !== "object" ||
value === null ||
!("name" in value) ||
typeof value.name !== "string"
) {
throw new TypeError("name must be a string");
}
return { name: value.name };
},
}),
]);
await dynamicRegistry.render(templateId, untrustedPayload);
Schema libraries fit this boundary without a package dependency:
parseProps: (value) => schema.parse(value). Parse errors are propagated to the
caller, and the parsed value is used for the subject, preview, and component.
createPreviewText truncates by Unicode code point so it never splits a surrogate
pair. Its maxLength must be a non-negative safe integer and defaults to 90.
Markdown content can be wrapped as an email template with
createMarkdownEmailTemplate.
Use the config integration when email theme tokens should be loaded through
@kleb/config:
import { kConfig } from "@kleb/config/server";
import { emailThemeConfig } from "@kleb/templating/config";
const loaded = kConfig("app.json").section("emailTheme", emailThemeConfig()).load();
Dependencies
Dependencies
| ID | Version |
|---|---|
| @react-email/components | ^1.0.12 |
| @react-email/render | ^2.0.10 |
Development dependencies
| ID | Version |
|---|---|
| @types/react | 19.2.17 |
| @types/react-dom | 19.2.3 |
| react | 19.2.7 |
| react-dom | 19.2.7 |
Peer dependencies
| ID | Version |
|---|---|
| @kleb/config | ^0.6.0 |
| react | ^18.0 || ^19.0 || ^19.0.0-rc |
| react-dom | ^18.0 || ^19.0 || ^19.0.0-rc |