diff --git a/packages/email-renderer/package.json b/packages/email-renderer/package.json
index 6adc8f3e..6c85c934 100644
--- a/packages/email-renderer/package.json
+++ b/packages/email-renderer/package.json
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/email-renderer",
- "version": "0.1.27",
+ "version": "0.1.28",
"type": "module",
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
"exports": {
diff --git a/packages/email-renderer/src/blocks.ts b/packages/email-renderer/src/blocks.ts
index 264e009e..e71a72a4 100644
--- a/packages/email-renderer/src/blocks.ts
+++ b/packages/email-renderer/src/blocks.ts
@@ -40,7 +40,7 @@ export const ampLayoutClass = (node: BlockNode) =>
export function ampLayoutCss(node: BlockNode, brandLinkColor?: string): string {
const props = attrs(node);
if (node.type === "content.button")
- return `.button.${ampLayoutClass(node)}{background-color:${buttonBackground(node, { brandLinkColor })}}`;
+ return `.button.${ampLayoutClass(node)}{background-color:${buttonBackground(node, { brandLinkColor })};${buttonVisualCss(node)}}`;
if (node.type === "layout.section") {
const background = safeCssValue(props.background, /^(#[0-9a-f]{3,8}|rgba?\([0-9., %]+\))$/i) || "#ffffff";
const padding = safeCssValue(props.padding, /^\d+(?:\.\d+)?(?:px|rem|em|%)?(?:\s+\d+(?:\.\d+)?(?:px|rem|em|%)?){0,3}$/) || "24px";
@@ -106,6 +106,16 @@ const buttonBackground = (node: BlockNode, context: unknown) => {
const brand = (context as { brandLinkColor?: string } | undefined)?.brandLinkColor;
return override || (brand && brandColor("#2563eb").parse(brand).ok ? brand : "#2563eb");
};
+const buttonVisuals = (node: BlockNode) => ({
+ color: safeCssValue(styleBase(node, "color"), /^(#[0-9a-f]{3,8}|rgba?\([0-9., %]+\))$/i) || "#ffffff",
+ fontSize: safeCssValue(styleBase(node, "fontSize"), /^\d+(?:\.\d+)?(?:px|rem|em|%)$/) || "16px",
+ fontWeight: safeCssValue(styleBase(node, "fontWeight"), /^(normal|bold|[1-9]00)$/) || "700",
+ radius: safeCssValue(styleBase(node, "borderRadius"), /^\d+(?:\.\d+)?(?:px|rem|em|%)$/) || "6px",
+});
+const buttonVisualCss = (node: BlockNode) => {
+ const values = buttonVisuals(node);
+ return `color:${values.color};font-size:${values.fontSize};font-weight:${values.fontWeight};border-radius:${values.radius}`;
+};
const emailCell = (body: string, node: BlockNode) =>
`
| ${body} |
`;
@@ -379,7 +389,7 @@ export const buttonBlock = leaf(
{
emailHtml: ({ node, context }) =>
emailCell(
- ``,
+ ``,
node,
),
ampEmail: ({ node }) =>
@@ -388,7 +398,7 @@ export const buttonBlock = leaf(
node,
),
mjml: ({ node, context }) =>
- `${html(attrs(node).label)}`,
+ `${html(attrs(node).label)}`,
},
({ node }) => `${String(attrs(node).label)}: ${String(attrs(node).href)}\n\n`,
);
diff --git a/packages/email-renderer/test/button-brand.test.ts b/packages/email-renderer/test/button-brand.test.ts
index a2eaf1f9..43642d54 100644
--- a/packages/email-renderer/test/button-brand.test.ts
+++ b/packages/email-renderer/test/button-brand.test.ts
@@ -2,6 +2,32 @@ import { expect, test } from "bun:test";
import type { BlockDocument } from "@wrnexus/blocks";
import { renderEmail, validateMjml } from "../src/index.ts";
+test("button typography and corners reach the clickable element in every target", async () => {
+ const document: BlockDocument = {
+ schemaVersion: 1, kind: "email", meta: { subject: "Styled CTA", lang: "en" }, theme: { tokens: {} },
+ root: { id: "root", type: "layout.email-document", version: 1, props: { width: "600" }, styles: {}, slots: {
+ content: [{ id: "cta", type: "content.button", version: 1, props: { label: "Read more", href: "https://example.com" }, styles: {
+ color: { base: "#123456" }, fontSize: { base: "22px" }, fontWeight: { base: "400" }, borderRadius: { base: "18px" },
+ }, slots: {} }],
+ } },
+ };
+ const before = structuredClone(document);
+ const result = await renderEmail(document, { amp: true, mjml: true, ampValidationTimeoutMs: 10000 });
+ expect(result.ok).toBe(true);
+ if (!result.ok) return;
+ const anchor = result.value.parts.html.content.match(/ {
for (const override of [undefined, "#aabbcc"]) {
const document: BlockDocument = {