---
title: Shell
description: Put a ready-made email editor page in your app, with a header, Export and Import buttons, messages and saving in the browser.
---

`<mail-quilt>` is only the editor. Around it, your app still needs somewhere to export the email, somewhere to show why an email was refused, and a way to keep the person's work. `<mail-quilt-shell>` is all of that in one element: the editor, with a header above it and a message bar between them.

## What it has

The header has the heading and up to three buttons. **Export HTML** downloads the email as `email.html`, ready to send. **Export JSON** downloads it as `email.json`, the [document format](/docs/document-format) the editor saves. **Import JSON** opens a `.json` file. If the editor refuses it, the message bar says why and the current email stays.

The message bar shows emails the editor refuses, exports that fail and saves that fail. Screen readers announce each message. The [Playground](/playground) is a Shell.

## With a bundler

```bash
npm install @mailquilt/editor
```

```html title="index.html"
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Email editor</title>
    <style>
      body {
        margin: 0;
      }

      /* The Shell fills the space you give it. */
      mail-quilt-shell {
        height: 100dvh;
      }
    </style>
  </head>
  <body>
    <mail-quilt-shell save-key="my-app:email"></mail-quilt-shell>
    <script type="module" src="./main.js"></script>
  </body>
</html>
```

```js title="main.js"
import "@mailquilt/editor";
import { starterEmail } from "./starter-email.js";

// Opens when nothing is saved under save-key yet.
document.querySelector("mail-quilt-shell").value = starterEmail;
```

Importing `@mailquilt/editor` registers both `<mail-quilt>` and `<mail-quilt-shell>`. On a page with no build step, load the CDN build as the [quick start](/docs/quick-start#without-a-bundler) shows. It registers both too.

The Shell fills the width and height of the space you give it, and the editor takes everything below the header. If the email is longer than that, the editor scrolls inside the Shell.

## React

```bash
npm install @mailquilt/react
```

```tsx title="app/page.tsx"
import { MailQuiltShell } from "@mailquilt/react";

import { starterEmail } from "./starter-email";

// A Server Component, as the Shell needs no callbacks to save the email.
const Page = () => (
  <div style={{ height: "100dvh" }}>
    <MailQuiltShell saveKey="my-app:email" value={starterEmail} />
  </div>
);

export default Page;
```

`MailQuiltShell` renders an empty `<mail-quilt-shell>` on the server, with its attributes, and the editor loads in the browser. Because `saveKey` keeps the email, this page can stay a Server Component. Pass `onChange` or `onError` from a Client Component, as with [`MailQuilt`](/docs/quick-start#react).

## Saving

With `save-key`, the Shell keeps the email in the browser's `localStorage` under that key:

- When the Shell opens, an email saved under the key opens instead of `value`. `value` is the email to start from when nothing is saved yet.
- While the saved email is open, setting `value` to nothing, or to that same starting email again, leaves it open. A React component that renders again, or gets its starting email after the Shell opens, doesn't replace the person's work. Setting a different email opens it as usual.
- If the saved email can't be opened, the message bar says why and `value` opens instead. The saved email stays as it is until the person changes something, so nothing is lost by opening the page.
- Each change is saved under the key. If the browser can't save, because storage is blocked or full, the message bar says so, and suggests Export JSON while that button shows.

Pick a key no other app on the same origin uses, such as one starting with your app's name. The Shell reads the key once, when it opens, so set it before then, as the examples do. Removing it later stops the saving.

Opening the saved email doesn't fire `mq-change`, because the person hasn't changed anything. If your app keeps its own copy of the email, read `value` from the element once it has loaded, such as from the `ref` in React, rather than assuming it's the email you passed.

Without `save-key`, the Shell stores nothing. Save the email yourself from `mq-change`, as the [quick start](/docs/quick-start) does. `mq-change` fires with or without `save-key`, so you can keep the email in the browser and on your server too.

## The Shell's API

| Name | What it does |
| --- | --- |
| `value` | The email the Shell shows, as on `<mail-quilt>`. With `save-key`, a saved email opens instead of the first one you set. |
| `heading` | The text in the header. It's "Email" when unset. |
| `actions` | The buttons to show, by name and separated by spaces: `import-json`, `export-json` and `export-html`. Leave it out to show all three. |
| `save-key` | Where the email is kept in the browser, as the `saveKey` property. Leave it out to store nothing. |
| `mq-change` | Fired after every change, including an import. The new email is in `event.detail.document`. |
| `mq-error` | Fired when the editor refuses an email you set as `value`. The reason is in `event.detail.reason`, and the message bar shows it too. |
| `exportHtml()` | Returns a promise of the email's HTML, as on `<mail-quilt>`. |

`value`, `mq-change`, `mq-error` and `exportHtml()` follow the rules the [quick start](/docs/quick-start#the-editors-api) describes, including when to listen for `mq-error`.

In React, these are the `value`, `heading`, `actions`, `saveKey`, `onChange` and `onError` props, with `actions` as an array of names, and `exportHtml()` on the `ref`.

## Customizing it

Set `heading` to name what's being edited, such as `heading="Welcome email"`.

Choose the buttons with `actions`. An app that keeps emails on its own server may want only Export HTML, so people don't move emails around as files:

```html
<mail-quilt-shell actions="export-html"></mail-quilt-shell>
```

```tsx
<MailQuiltShell actions={["export-html"]} />
```

The buttons keep their own order, whatever order you name them in, and `actions=""` shows none. Without Export JSON, a failed save says the browser can't save without suggesting it. If you leave out Export HTML, call `exportHtml()` from a control of your own, so people can still get the HTML.

The Shell draws its header and messages from the same [theme tokens](/docs/theme-tokens) as the editor. Set them on `<mail-quilt-shell>`, and the editor inside uses them too.

To restyle the Shell beyond the tokens, each piece of the header and the message bar is a CSS part. Style it from your own stylesheet with `::part()`:

| Part          | What it is                                       |
| ------------- | ------------------------------------------------ |
| `header`      | The bar above the editor.                        |
| `heading`     | The heading's text.                              |
| `actions`     | The group of buttons on the right of the header. |
| `import-json` | The Import JSON button, while it shows.          |
| `export-json` | The Export JSON button, while it shows.          |
| `export-html` | The Export HTML button, while it shows.          |
| `messages`    | The message bar, below the header.               |
| `message`     | Each message in the bar.                         |

```css title="styles.css"
mail-quilt-shell::part(header) {
  background: #f8fafc;
}

mail-quilt-shell::part(heading) {
  font-size: 16px;
}
```

## Shell or `<mail-quilt>`?

Use the Shell when the editor is a page of its own and its header can be the page's controls. It gets an editor page working with a few lines, and a refused email or a failed save always reaches the person.

Use `<mail-quilt>` on its own when the editor sits inside a page your app already lays out, such as beside a form with the email's subject and recipients, or when you want buttons of your own in the header. You can choose and restyle the Shell's buttons, but you can't add to them.
