Partials

Share layout and repeated blocks across templates with partials that compile into each template and cost nothing at send time.

A partial is a function from props to JSX, in any project file. The compiler inlines it, so a partial adds nothing at send time and every template that uses it picks up an edit at its next publish.

partials/order-row.tsx
import { fmt } from "@samva/markup/fmt";

export const OrderRow = ({
  item,
  currency,
}: {
  item: { title: string; qty: number; price: number };
  currency: string;
}) => (
  <p className="my-2">
    {item.title} x {item.qty}: {fmt.money(item.price * item.qty, currency)}
  </p>
);
templates/receipt.tsx
import { defineTemplate } from "@samva/markup";
import { Email, Section } from "@samva/markup/email/components";
import { jsonSchema } from "@samva/markup/input-schema";

import { OrderRow } from "../partials/order-row";

export default defineTemplate({
  id: "receipt",
  schema: jsonSchema<{
    currency: string;
    items: { title: string; qty: number; price: number }[];
  }>({
    type: "object",
    properties: {
      currency: { type: "string" },
      items: {
        type: "array",
        items: {
          type: "object",
          properties: {
            title: { type: "string" },
            qty: { type: "number" },
            price: { type: "number" },
          },
          required: ["title", "qty", "price"],
          additionalProperties: false,
        },
      },
    },
    required: ["currency", "items"],
    additionalProperties: false,
  }),
  fixtures: {
    default: { currency: "USD", items: [{ title: "Linen shirt", qty: 2, price: 42 }] },
  },
  email: {
    subject: () => "Your receipt",
    body: (input) => (
      <Email>
        <Section>
          {input.items.map((item) => (
            <OrderRow item={item} currency={input.currency} />
          ))}
        </Section>
      </Email>
    ),
  },
});
  • Props are bindings too. item.title is checked against the schema through the call site, so a misspelled field fails in the template that passes the wrong data.
  • Arithmetic on bound numbers, like item.price * item.qty, is allowed inside formatter arguments and conditions. It is the one computation the profile permits, because it is total and deterministic.
  • A partial may take children, which is how layouts compose: <Card title="Your order">...</Card>.
  • A partial that renders itself is rejected. Nesting is limited by the IR's depth limit.
  • The partials/ folder is a convention. Any file inside the project can be imported.

In the visual editor, opening a partial shows a warning that an edit applies to every template that uses it.

Related documentation