---
title: Layout
description: Übergeben Sie eine JSON-Hülle mit einem Slot. Vorschau und Export legen die E-Mail des Autors hinein. Speichern tut das nicht.
---

# Layout

Übergeben Sie ein JSON-Dokument mit genau einem `slot`. Vorschau und Export legen die E-Mail des Autors in diese Hülle. Speichern tut das nicht: `getContent()` ist nur die verfasste Vorlage.

```ts
init({ layout?: TemplateContent; sectionWrapper?: boolean })
renderToMjml(content, { layout?: TemplateContent })
```

Bauen Sie die Hülle mit `createSlotBlock()` und, für eine Karte, `createWrapperBlock()`. `createBlock('slot')` und `createBlock('wrapper')` werfen. `sectionWrapper` ist Editor-Chrome und unabhängig von `layout`. `renderToMjml` nimmt `sectionWrapper` nicht entgegen.

## Beispiel

Grauer Seitenhintergrund, eine Zeile „Im Browser ansehen“, die Autorensektionen in einer weißen Karte, Impressum unter der Karte. Das ist die umschließende Hülle: Vorschau und `toMjml()` / `toHtml()` setzen sie zusammen; die Bearbeitungsleinwand und `getContent()` nicht.

```ts
import {
  init,
  createSlotBlock,
  createWrapperBlock,
  createParagraphBlock,
  createDefaultTemplateContent,
} from '@templatical/editor';

const layout = createDefaultTemplateContent();
layout.settings.backgroundColor = '#f3f4f6';
layout.blocks = [
  createParagraphBlock({
    content:
      '<p style="text-align:center"><a href="https://example.com/view">Im Browser ansehen</a></p>',
  }),
  createWrapperBlock({
    styles: {
      backgroundColor: '#ffffff',
      padding: { top: 24, right: 24, bottom: 24, left: 24 },
    },
    borderRadius: 12,
    children: [createSlotBlock()],
  }),
  createParagraphBlock({
    content:
      '<p style="text-align:center"><a href="https://example.com/imprint">Impressum</a></p>',
  }),
];

const editor = await init({
  container: '#editor',
  layout,
  sectionWrapper: false,
});
```

```
mj-body                         ← grauer Untergrund (layout.settings.backgroundColor)
  mj-section                    ← Im Browser ansehen
  mj-wrapper                    ← weiße Karte
    [Autorensektionen…]
  mj-section                    ← Impressum
```

`sectionWrapper: false` blendet **Wrapper hinzufügen** an Autorensektionen aus. Sitzt der Slot in einem `wrapper`, würde das Steuerelement `mj-wrapper` in `mj-wrapper` erzeugen, was MJML verbietet. Lassen Sie `sectionWrapper` weg (oder übergeben Sie `true`), wenn Sie das Panel wollen; der Editor deaktiviert das Einschalten bei diesem Karten-Layout trotzdem.

## Der Vertrag

`layout` ist ein `TemplateContent` mit genau einem `slot`.

<!-- prettier-ignore -->
| Fläche | Mit `layout` |
| --- | --- |
| `getContent()` / `setContent()` / load / save / `onChange` / Verlauf | nur verfasste Vorlage |
| Bearbeitungsleinwand | nur verfasste Vorlage |
| Wrapper-Panel der Sektions-Symbolleiste | [Im Editor](#im-editor) |
| `renderToMjml(content)` (ohne `layout`-Argument) | nur verfasste Vorlage |
| Vorschauleinwand | zusammengesetzt |
| `editor.toMjml()` / `toHtml()` | zusammengesetzt |
| `RenderPayload.content` | zusammengesetzt |

`init` führt `validateLayout` auf der Hülle aus (nach Merge-Tag-Normalisierung). `applyLayout` läuft in der Vorschau und bei `toMjml` / `toHtml` / `renderToMjml(content, { layout })`. Es klont: IDs der Inhaltsblöcke bleiben; Layout-Block-IDs sind auf dem Klon neu.

::: tip Gespeichertes JSON
`getContent()`, load, save und die Bearbeitungsleinwand enthalten nie Layout-Blöcke. Versenden Sie mit `toMjml()` / `toHtml()`, oder rufen Sie `applyLayout` auf dem Server auf. Das Ausblenden von Wrapper hinzufügen entfernt `section.wrapper` nicht aus dem gespeicherten Inhalt.
:::

**`slot`** ist die Stelle, an der `content.blocks` landen. Genau eines im Layout-Baum. Zulässig als Kind von `layout.blocks` (oberste Ebene) oder von `wrapper.children`. Unzulässig in einer Sektionsspalte, in einem verschachtelten Wrapper und im Editor-Inhalt. Die Palette lässt ihn aus. Ein `slot`, der `renderToMjml` ohne `layout` erreicht, wirft.

**`wrapper`** ist die Layout-Karte: `styles.backgroundColor` / `styles.padding` / `borderRadius` / `backgroundImage` werden zu `mj-wrapper`. In Outlook unter Windows überdeckt die Hintergrundfarbe eines Bands das Hintergrundbild einer darin liegenden Sektion — setzen Sie das Bild auf das Band, oder lassen Sie das Band ohne Farbe. Der Linter des Editors sieht das Layout nicht und kann deshalb nicht davor warnen. Nur im Layout. Im Editor-Inhalt abgelehnt (`setContent` / `load` / `addBlock` / `createBlock('wrapper')`). Autorinnen nutzen weiterhin `section.wrapper` als Kurzform pro Sektion.

Layout-Factories und -Helfer (`createSlotBlock`, `createWrapperBlock`, `createParagraphBlock`, `createDefaultTemplateContent`, `applyLayout`, `validateLayout`, `isSlot`, `isWrapper`, `layoutWrapsSlot`) werden aus `@templatical/editor` exportiert. Headless-Rendering kann `applyLayout` aus `@templatical/types` ohne den Editor importieren.

`validateLayout` wirft:

```
[Templatical] layout: must contain exactly one slot block
[Templatical] layout: slot must be a top-level or wrapper child, not nested in a section
[Templatical] layout: a wrapper cannot contain a wrapper
```

`setContent` / `load` / `addBlock` lehnen `slot` und `wrapper` im Inhalt ab.

MJML verbietet `mj-wrapper` in `mj-wrapper`. Sitzt der Slot in einem Wrapper, durchläuft `applyLayout` die eingeschleusten `content.blocks`. Jeder Block, der `mj-wrapper` erzeugen würde — `section.wrapper` gesetzt oder `type === 'wrapper'` — wirft:

```
[Templatical] layout: a wrapper around the slot cannot contain blocks that emit mj-wrapper (section.wrapper)
```

Ein Slot in `children: [[slot]]` ist `mj-section` in `mj-column`, was MJML ebenfalls verbietet. Der Slot bleibt Kind des Body oder eines Wrappers.

## Kopf und Fuß

Keine Karte: der Slot ist Geschwister von Kopf und Fuß. Autor-`section.wrapper` ist dann ein Geschwister-`mj-wrapper` unter `mj-body` — gültig. Lassen Sie `sectionWrapper` weg, wenn Autorinnen weiterhin Wrapper hinzufügen sollen.

```ts
layout.blocks = [
  createParagraphBlock({
    content:
      '<p style="text-align:center"><a href="https://example.com/view">Im Browser ansehen</a></p>',
  }),
  createSlotBlock(),
  createParagraphBlock({
    content:
      '<p style="text-align:center"><a href="https://example.com/imprint">Impressum</a></p>',
  }),
];
```

```
mj-body                         ← grauer Untergrund
  mj-section                    ← Im Browser ansehen
  [Autorensektionen…]            ← darf section.wrapper enthalten
  mj-section                    ← Impressum
```

## Im Editor

Das Wrapper-Panel ist der Schalter **Wrapper hinzufügen** in der Sektions-Symbolleiste plus Farbe / Padding / Radius, wenn er an ist. Die Deaktivierung bei Karten-Layout und `sectionWrapper` sind unabhängig: `layout` zu setzen blendet das Panel nicht aus. Eine Hülle aus Kopf / Slot / Fuß bietet Wrapper hinzufügen weiterhin an.

Wenn `layout` gesetzt ist und der Slot in einem `wrapper` sitzt, kann der Schalter nicht eingeschaltet werden. Darunter steht eine gedämpfte Zeile:

> Dieser Editor rahmt die E-Mail bereits ein. Ein zusätzlicher Rahmen für diesen Abschnitt wird nicht unterstützt — Vorschau und Export schlagen fehl.

Hat eine geladene Vorlage bereits `section.wrapper`, bleibt der Schalter an und aktiviert, damit er ausgeschaltet werden kann. Der Hinweis bleibt.

```ts
init({ sectionWrapper?: boolean })
```

<!-- prettier-ignore -->
| Wert | Panel |
| --- | --- |
| weggelassen / `true` | sichtbar, plus die Deaktivierung bei Karten-Layout oben |
| `false` | ausgeblendet, auch dort, wo Wrapper hinzufügen zulässig ist (Kopf / Slot / Fuß, oder kein Layout) |

`sectionWrapper: false` entfernt `section.wrapper` nicht aus dem Inhalt, lehnt `updateBlock` nicht ab und ändert `getContent()` nicht. Ausblenden ändert nie einen Wert.

Wenn `sectionWrapper === false` und die ausgewählte Sektion bereits `wrapper` hat, wird das Panel weiterhin gerendert und der Schalter bleibt aktiviert, damit er ausgeschaltet werden kann. Ist er aus, verschwindet das Panel. Der Hinweis zum Karten-Layout erscheint weiterhin, wenn das Panel sichtbar ist.

## Einstellungen

Layout-`settings` ist ein vollständiges `TemplateSettings`. Gelesen wird nur `backgroundColor`.

<!-- prettier-ignore -->
| Feld | Gewinner |
| --- | --- |
| `backgroundColor` | Layout → `mj-body` |
| `width`, `fontFamily`, `textColor`, `linkColor`, `linkUnderline`, `locale`, `preheaderText`, `direction` | Inhalt |

Inhalts-`settings.backgroundColor` wird nicht verändert. Die Template-Einstellungen bearbeiten es weiterhin. Es ist nicht `mj-body`, wenn ein Layout angewendet wird. `direction` kommt aus der verfassten Vorlage. Layout setzt kein `dir`.

Sie können die Einstellungen des Inhalts auf das Layout kopieren und `backgroundColor` überschreiben.

## Headless-Nutzung

```ts
import { renderToMjml } from '@templatical/renderer';
import { applyLayout } from '@templatical/types';

const mjml = await renderToMjml(content, { layout });
const composed = applyLayout(layout, content);
```

Vorschau, `toMjml` und `toHtml` setzen zuerst zusammen, dann optional `resolvePreview`:

```
base = applyLayout(layout, content)
if (resolvePreview) base = await resolvePreview({ content: base, recipient })
```

Die Bearbeitungsleinwand nimmt diesen Pfad nie. Das Zusammensetzen des Layouts ist synchron.

Wenn `layout` gesetzt ist, ist `PreviewResolveContext.content` das zusammengesetzte Dokument. Geben Sie diese Form zurück. Nur den inneren `content` zurückzugeben, lässt die Hülle weg.

`renderToMjml(content)` ohne `layout`-Argument ruft `applyLayout` nicht auf.

Test-E-Mail: `payload.content` ist die verfasste Vorlage; MJML/HTML aus `toMjml` / `toHtml` enthält die Hülle.

Lint läuft über den Editor-Inhalt. Die Hülle wird nicht gelintet.

Ein Versandpfad, der gespeichertes JSON ohne `layout` neu rendert, lässt die Hülle weg. Übergeben Sie dasselbe `layout` an `renderToMjml` (oder rufen Sie `applyLayout` auf) auf dem Server.

## Im Playground

Das Setup **Layout** legt eine Kartenhülle um die E-Mail. Sie erscheint in der Vorschau und im MJML-Export, nie im gespeicherten JSON.

[Im Playground öffnen](https://play.templatical.com/scenes/layout)
