# EmailTemplates

> Email template gallery with rendered thumbnails, an editor (name, category, subject, preview text, rich text body, variables) and a sandboxed live preview at desktop or phone width with test send.

Source: https://docs.nasaqui.com/components/email-templates

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/email-templates.json
```

Design the messages your app sends. `EmailTemplates` shows a searchable gallery of template cards (each with a
thumbnail of the rendered email); opening one shows the editor with a live preview beside it. The pieces are
exported on their own: `EmailTemplateGallery`, `EmailTemplateEditor` and `EmailTemplatePreview`.

## Bundle cost

The editor uses [`RichTextEditor`](https://docs.nasaqui.com/components/rich-text-editor) (Tiptap, roughly 150-200 KB minified). Lazy-load
`EmailTemplateEditor` where it matters. The gallery and preview do not load it.

## When to use

- A settings screen for transactional, marketing and notification emails.
- Showing one template read-only: `EmailTemplatePreview`.

## When not to use

- Sending mail or storing templates: this is presentational. You own the async callbacks.
- Drag-and-drop layout builders: the body is a rich text document, not a block canvas.

## Import

```tsx
import { EmailTemplates, type EmailTemplate, type EmailVariable } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
const variables: EmailVariable[] = [{ key: "first_name", label: "First name", sample: "Sara" }];

<EmailTemplates
  templates={templates}
  variables={variables}
  sender={{ name: "The team", email: "hello@example.com" }}
  onSave={async (t) => {
    await api.saveTemplate(t);
  }}
  onSendTest={async (t, email) => api.sendTest(t.id, email)}
/>;
```

## Anatomy

- Gallery: search, category tabs, cards (thumbnail, name, subject, status, category, updated), duplicate and delete (delete asks first).
- Editor: name, category, subject, preview text, body, direction, variable list with copy buttons, unknown-variable warning, Save (enabled when changed), Send test.
- Preview: sender, recipient and subject header, then the email in a sandboxed frame. Desktop and mobile widths.

## API

`EmailTemplate` is `{ id, name, category, status, subject, preheader?, body (HTML), dir?, footer?, updatedAt? }`;
`category` is `"welcome" | "transactional" | "marketing" | "notification"`, `status` is `"draft" | "active"`.

| Prop (`EmailTemplates`) | Type | Description |
| --- | --- | --- |
| `templates` | `EmailTemplate[]` | The list. Controlled: update it after each callback resolves. |
| `variables` | `EmailVariable[]` | `{ key, label, sample }`. `{{key}}` in the subject, preview text, body and footer is replaced with `sample` in previews. |
| `defaultSelectedId` | `string` | Open this template in the editor first. |
| `sender`, `recipient` | | Shown in the preview header; `recipient` is also the default test address. |
| `onSave` | `(t) => Promise<void \| { error? }>` | Also called for a new template (from "New template") with a temporary id. Add it to `templates` when it resolves. |
| `onDuplicate`, `onDelete` | `(t) => Promise<void \| { error? }>` | Buttons appear only when given. |
| `onSendTest` | `(t, email) => Promise<void \| { error? }>` | Shows "Send test". |
| `labels` | `Partial<EmailTemplatesLabels>` | Override any string. |

Pure helpers with no React: `fillVariables`, `findVariables`, `unknownVariables`, `escapeHtml`, `renderEmailDocument`.

## Safety

Previews render in an `<iframe sandbox="">`, so no script from a body can run. Variable values are HTML-escaped
before they are substituted. The body editor only produces Tiptap's own schema (links limited to http, https, mailto, tel).

## Accessibility

Each card is one button named by the template name. Thumbnails are decorative (`aria-hidden`, not focusable). The
preview frame has a title. Delete uses a confirmation dialog. Save, test and error messages appear in an alert.

## RTL & i18n

- English and Arabic strings ship. Each template has its own `dir`, so an Arabic email previews right to left even in an English app.
- Variable keys, sender addresses and the test address are set left-to-right.
- The back arrow mirrors.

## Styling & tokens

- App chrome uses Nasaq tokens. The email document itself is paper-white with fixed `rgb()` colours, because email clients do not follow the app theme.

## Do / Don't

- Do provide sample values for every variable so the preview reads like a real email.
- Don't put secrets in a template; previews and thumbnails render the body in the browser.

## Related

- [`RichTextEditor`](https://docs.nasaqui.com/components/rich-text-editor)
- [`CopyButton`](https://docs.nasaqui.com/components/copy-button)

## Lab

https://docs.nasaqui.com/?path=/docs/components-editors-email-templates--docs

## Code

### React

```tsx
const variables: EmailVariable[] = [{ key: "first_name", label: "First name", sample: "Sara" }];

<EmailTemplates
  templates={templates}
  variables={variables}
  sender={{ name: "The team", email: "hello@example.com" }}
  onSave={async (t) => {
    await api.saveTemplate(t);
  }}
  onSendTest={async (t, email) => api.sendTest(t.id, email)}
/>;
```

### shadcn

```tsx
const variables: EmailVariable[] = [{ key: "first_name", label: "First name", sample: "Sara" }];

<EmailTemplates
  templates={templates}
  variables={variables}
  sender={{ name: "The team", email: "hello@example.com" }}
  onSave={async (t) => {
    await api.saveTemplate(t);
  }}
  onSendTest={async (t, email) => api.sendTest(t.id, email)}
/>;
```

### Vue

```vue
<script setup lang="ts">
import { NqEmailTemplates, type EmailTemplate, type EmailVariable } from "@fadymondy/nasaq/vue";
import { ref } from "vue";

const variables: EmailVariable[] = [{ key: "first_name", label: "First name", sample: "Sara" }];

const templates = ref<EmailTemplate[]>([
  { id: "t1", name: "Welcome", category: "welcome", status: "active", subject: "Welcome, {{first_name}}", preheader: "Your account is ready.", body: "<h2>Hello {{first_name}}</h2><p>Thanks for joining us.</p>", updatedAt: "2026-09-20T10:00:00Z" },
  { id: "t2", name: "Order receipt", category: "transactional", status: "draft", subject: "Your receipt", body: "<p>Thanks for your order, {{first_name}}.</p>", updatedAt: "2026-09-22T10:00:00Z" },
]);

async function save(t: EmailTemplate) {
  const i = templates.value.findIndex((x) => x.id === t.id);
  templates.value = i < 0 ? [...templates.value, t] : templates.value.map((x) => (x.id === t.id ? t : x));
}
async function sendTest(_t: EmailTemplate, _email: string) {
  // Your request.
}
</script>

<template>
  <NqEmailTemplates :templates="templates" :variables="variables" :sender="{ name: 'The team', email: 'hello@example.com' }" :on-save="save" :on-send-test="sendTest" />
</template>
```

### Blade

```blade
@php
    $variables = [['key' => 'first_name', 'label' => 'First name', 'sample' => 'Sara']];
    $templates = [
        ['id' => 't1', 'name' => 'Welcome', 'category' => 'welcome', 'status' => 'active', 'subject' => 'Welcome, {{first_name}}', 'preheader' => 'Your account is ready.', 'body' => '<h2>Hello {{first_name}}</h2><p>Thanks for joining us.</p>', 'updatedAt' => '2026-09-20T10:00:00Z'],
        ['id' => 't2', 'name' => 'Order receipt', 'category' => 'transactional', 'status' => 'draft', 'subject' => 'Your receipt', 'body' => '<p>Thanks for your order, {{first_name}} {{oops}}.</p>', 'updatedAt' => '2026-09-22T10:00:00Z'],
    ];
@endphp
<x-nq::email-templates :templates="$templates" :variables="$variables" :sender="['name' => 'The team', 'email' => 'hello@example.com']"
    @nq-email-template-save="$event.detail.promise = api.saveTemplate($event.detail.template)"
    @nq-email-template-send-test="$event.detail.promise = api.sendTest($event.detail.template.id, $event.detail.email)" />
```

### HTML + Alpine

```html
<div data-slot="email-templates" x-data="nqEmailTemplates(JSON.parse('{\u0022templates\u0022:[{\u0022id\u0022:\u0022t1\u0022,\u0022name\u0022:\u0022Welcome\u0022,\u0022category\u0022:\u0022welcome\u0022,\u0022status\u0022:\u0022active\u0022,\u0022subject\u0022:\u0022Welcome, {{first_name}}\u0022,\u0022preheader\u0022:\u0022Your account is ready.\u0022,\u0022body\u0022:\u0022\\u003Ch2\\u003EHello {{first_name}}\\u003C\\\/h2\\u003E\\u003Cp\\u003EThanks for joining us.\\u003C\\\/p\\u003E\u0022,\u0022updatedAt\u0022:\u00222026-09-20T10:00:00Z\u0022},{\u0022id\u0022:\u0022t2\u0022,\u0022name\u0022:\u0022Order receipt\u0022,\u0022category\u0022:\u0022transactional\u0022,\u0022status\u0022:\u0022draft\u0022,\u0022subject\u0022:\u0022Your receipt\u0022,\u0022body\u0022:\u0022\\u003Cp\\u003EThanks for your order, {{first_name}} {{oops}}.\\u003C\\\/p\\u003E\u0022,\u0022updatedAt\u0022:\u00222026-09-22T10:00:00Z\u0022}],\u0022variables\u0022:[{\u0022key\u0022:\u0022first_name\u0022,\u0022label\u0022:\u0022First name\u0022,\u0022sample\u0022:\u0022Sara\u0022}],\u0022sender\u0022:{\u0022name\u0022:\u0022The team\u0022,\u0022email\u0022:\u0022hello@example.com\u0022},\u0022locale\u0022:\u0022en\u0022,\u0022canSave\u0022:true,\u0022canDuplicate\u0022:true,\u0022canDelete\u0022:true,\u0022canSendTest\u0022:true,\u0022labels\u0022:{\u0022gallery\u0022:\u0022Email templates\u0022,\u0022search\u0022:\u0022Search templates\\u2026\u0022,\u0022newTemplate\u0022:\u0022New template\u0022,\u0022untitled\u0022:\u0022Untitled template\u0022,\u0022all\u0022:\u0022All\u0022,\u0022updated\u0022:\u0022Updated\u0022,\u0022noneTitle\u0022:\u0022No templates yet\u0022,\u0022noneHint\u0022:\u0022Create a template to reuse the same message everywhere.\u0022,\u0022noMatchTitle\u0022:\u0022No templates match\u0022,\u0022noMatchHint\u0022:\u0022Try another search or category.\u0022,\u0022duplicate\u0022:\u0022Duplicate\u0022,\u0022delete\u0022:\u0022Delete\u0022,\u0022back\u0022:\u0022All templates\u0022,\u0022save\u0022:\u0022Save template\u0022,\u0022saved\u0022:\u0022Template saved.\u0022,\u0022unsaved\u0022:\u0022Unsaved changes\u0022,\u0022name\u0022:\u0022Template name\u0022,\u0022category\u0022:\u0022Category\u0022,\u0022subject\u0022:\u0022Subject\u0022,\u0022preheader\u0022:\u0022Preview text\u0022,\u0022preheaderHint\u0022:\u0022The grey line shown after the subject in most inboxes.\u0022,\u0022body\u0022:\u0022Body\u0022,\u0022direction\u0022:\u0022Direction\u0022,\u0022ltr\u0022:\u0022Left to right\u0022,\u0022rtl\u0022:\u0022Right to left\u0022,\u0022variables\u0022:\u0022Variables\u0022,\u0022variablesHint\u0022:\u0022Copy a variable and paste it into the subject or the body. It is replaced when the email is sent.\u0022,\u0022copyVariable\u0022:\u0022Copy {key}\u0022,\u0022unknown\u0022:\u0022Unknown variables: {keys}\u0022,\u0022edit\u0022:\u0022Edit\u0022,\u0022preview\u0022:\u0022Preview\u0022,\u0022desktop\u0022:\u0022Desktop\u0022,\u0022mobile\u0022:\u0022Mobile\u0022,\u0022from\u0022:\u0022From\u0022,\u0022to\u0022:\u0022To\u0022,\u0022sendTest\u0022:\u0022Send test\u0022,\u0022sendTestTitle\u0022:\u0022Send a test email\u0022,\u0022sendTestBody\u0022:\u0022Sends this template with the sample values to one address.\u0022,\u0022email\u0022:\u0022Email address\u0022,\u0022send\u0022:\u0022Send\u0022,\u0022cancel\u0022:\u0022Cancel\u0022,\u0022sent\u0022:\u0022Test email sent.\u0022,\u0022invalidEmail\u0022:\u0022Enter a valid email address.\u0022,\u0022previewFrame\u0022:\u0022Email preview\u0022,\u0022thumbnail\u0022:\u0022Preview of {name}\u0022,\u0022deleteConfirm\u0022:\u0022Delete {name}?\u0022,\u0022deleteBody\u0022:\u0022This cannot be undone.\u0022,\u0022failed\u0022:\u0022Something went wrong. Try again.\u0022,\u0022htmlBody\u0022:\u0022Body (HTML)\u0022,\u0022categories\u0022:{\u0022welcome\u0022:\u0022Welcome\u0022,\u0022transactional\u0022:\u0022Transactional\u0022,\u0022marketing\u0022:\u0022Marketing\u0022,\u0022notification\u0022:\u0022Notification\u0022},\u0022statuses\u0022:{\u0022draft\u0022:\u0022Draft\u0022,\u0022active\u0022:\u0022Active\u0022}}}'))" x-modelable="templates" @nq-email-template-save="$event.detail.promise = api.saveTemplate($event.detail.template)" @nq-email-template-send-test="$event.detail.promise = api.sendTest($event.detail.template.id, $event.detail.email)">
    <template x-if="draft">
        <div data-slot="email-template-editor" class="flex min-w-0 flex-col gap-4">
            <div class="flex flex-wrap items-center gap-2">
                <button data-slot="button"
     type="button"                         x-on:click="back()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100 rtl:rotate-180" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m12 19-7-7 7-7"/>
  <path d="M19 12H5"/>
</svg>                    All templates</button>
                <span class="ms-auto flex flex-wrap items-center gap-2">
                    <span x-show="dirty" style="display: none" class="text-caption text-muted-foreground">Unsaved changes</span>
                    <template x-if="canSendTest">
                        <button data-slot="button"
     type="button"                         x-on:click="openTest()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/>
  <path d="m21.854 2.147-10.94 10.939"/>
</svg>                            Send test</button>
                    </template>
                    <template x-if="canSave">
                        <button data-slot="button"
     type="button"                         x-on:click="save()" x-bind:aria-busy="saving ? &#039;true&#039; : null" x-bind:disabled="! dirty || saving ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <span x-show="saving" style="display: none"><svg data-slot="spinner" aria-hidden="true" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg></span>
                            Save template</button>
                    </template>
                </span>
            </div>
            <template x-if="message && message.tone === 'danger'"><div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="message.text"></span></div>
            </div>
    </div>
</template>
            <template x-if="message && message.tone === 'success'"><div data-slot="alert" data-tone="success" role="status"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-success/30 bg-nq-success-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="message.text"></span></div>
            </div>
    </div>
</template>
            <div class="flex gap-1 lg:hidden" role="tablist" aria-label="Email templates">
                <button data-slot="button"
     type="button"                         role="tab" x-bind:variant="pane === &#039;edit&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="pane === &#039;edit&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="pane = &#039;edit&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Edit</button>
                <button data-slot="button"
     type="button"                         role="tab" x-bind:aria-selected="pane === &#039;preview&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="pane = &#039;preview&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Preview</button>
            </div>
            <div class="grid min-w-0 gap-6 lg:grid-cols-2">
                <div class="flex min-w-0 flex-col gap-4 max-lg:[&[data-pane=preview]]:hidden" x-bind:data-pane="pane">
                    <div class="grid gap-4 sm:grid-cols-2">
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Template name</label>
                            <input data-slot="input" type="text"
                    x-model="draft.name" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
</div>
                        <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Category</label>
                            <div data-slot="select" x-data="nqSelect(null, false)" x-modelable="value" x-id="['nq-select']" x-model="draft.category" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? ''" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                                <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('welcome', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('welcome')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Welcome</span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('transactional', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('transactional')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Transactional</span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('marketing', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('marketing')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Marketing</span>
</div>
                                                                            <div data-slot="select-item" x-bind="item('notification', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('notification')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Notification</span>
</div>
    </div>
</template>
    </div>
</div>
                    </div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Subject</label>
                        <input data-slot="input" type="text"
                    x-model="draft.subject" dir="auto" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
</div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Preview text</label>
                        <input data-slot="input" type="text"
                    x-model="draft.preheader" dir="auto" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control">
                        <p data-slot="field-description" class="text-caption text-muted-foreground">The grey line shown after the subject in most inboxes.</p>
</div>
                    <div class="flex flex-col gap-1.5" x-on:focusin.capture="touched = true">
                        <span id="nq-et-000001-body" class="text-label text-foreground">Body</span>
                                                    <textarea data-slot="textarea"
                x-model="draft.body" dir="ltr" rows="10" aria-label="Body (HTML)" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-foreground transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] min-h-20 py-2 font-mono text-code"></textarea>
                                            </div>
                    <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Direction</label>
                        <div data-slot="select" x-data="nqSelect(null, false)" x-modelable="value" x-id="['nq-select']" x-model="draft.dir" class="contents">
    <button data-slot="select-trigger" x-ref="trigger" x-bind="trigger"
        class="flex h-control w-full min-w-0 items-center justify-between gap-2 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] cursor-default select-none outline-none transition-colors duration-150 ease-nq focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-popup-open:border-nq-focus data-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px]">
    <span data-slot="select-value" x-text="label() ?? ''" :data-placeholder="empty() ? '' : undefined"
    class="min-w-0 flex-1 truncate text-start data-placeholder:text-muted-foreground"></span>
    <span class="flex shrink-0 text-muted-foreground [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg></span>
</button>
                            <template x-teleport="body">
    <div data-slot="select-content" x-ref="popup" x-bind="popup" x-nq-presence="open" x-anchor.bottom-start.offset.4="$refs.trigger"
        class="z-50 min-w-[var(--anchor-width)] max-h-[var(--available-height)] overflow-y-auto rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
        <div data-slot="select-item" x-bind="item('ltr', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('ltr')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Left to right</span>
</div>
                                <div data-slot="select-item" x-bind="item('rtl', false)"
    class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control ps-8 pe-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="isSelected('rtl')" x-cloak class="contents"><svg class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    <span data-slot="select-item-text" class="min-w-0 flex-1 truncate">Right to left</span>
</div>
    </div>
</template>
    </div>
</div>
                    <section x-show="variables.length" style="display: none" aria-labelledby="nq-et-000001-vars" class="flex flex-col gap-2">
                        <div>
                            <h3 id="nq-et-000001-vars" class="text-label text-foreground">Variables</h3>
                            <p class="text-body-sm text-muted-foreground">Copy a variable and paste it into the subject or the body. It is replaced when the email is sent.</p>
                        </div>
                        <ul class="flex flex-wrap gap-2">
                                                            <li class="flex items-center gap-1 rounded-control border border-border bg-card ps-2 text-body-sm">
                                    <span class="text-muted-foreground">First name</span>
                                    <bdi dir="ltr" class="font-mono text-code text-foreground">{{first_name}}</bdi>
                                    <span class="contents" x-data="nqCopyButton('{{first_name}}', 1500)">
    <button data-slot="copy-button"
     type="button"                         aria-label="Copy first_name" x-on:click="copy()" x-bind:data-copied="state === &#039;copied&#039; ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0 data-copied:text-nq-success-text">
        <svg aria-hidden="true" x-show="state !== 'copied'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>        <svg aria-hidden="true" x-show="state === 'copied'" style="display: none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></button>
    <span data-slot="copy-button-status" role="status" aria-live="polite" class="sr-only"
        data-copied-label="Copied to clipboard" data-failed-label="Could not copy"
        x-text="state === 'copied' ? $el.dataset.copiedLabel : state === 'failed' ? $el.dataset.failedLabel : ''"></span>
</span>
                                </li>
                                                    </ul>
                        <template x-if="unknown.length">
                            <div data-slot="alert" data-tone="warning" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-warning/30 bg-nq-warning-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-warning-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <line x1="12" x2="12" y1="8" y2="12"/>
  <line x1="12" x2="12.01" y1="16" y2="16"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="unknownText"></span></div>
            </div>
    </div>
                        </template>
                    </section>
                </div>
                <div class="min-w-0 max-lg:[&[data-pane=edit]]:hidden" x-bind:data-pane="pane">
                    <div data-slot="email-template-preview" class="flex min-w-0 flex-col gap-3">
                        <div class="flex items-center justify-between gap-2">
                            <span class="text-label text-foreground">Preview</span>
                            <div role="group" aria-label="Preview" class="inline-flex gap-0.5 rounded-control bg-nq-surface-soft p-0.5">
                                <button data-slot="button"
     type="button"                         aria-label="Desktop" x-bind:variant="device === &#039;desktop&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-pressed="device === &#039;desktop&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="device = &#039;desktop&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg data-slot="icon" aria-hidden="true" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="20" height="14" x="2" y="3" rx="2"/>
  <line x1="8" x2="16" y1="21" y2="21"/>
  <line x1="12" x2="12" y1="17" y2="21"/>
</svg></button>
                                <button data-slot="button"
     type="button"                         aria-label="Mobile" x-bind:variant="device === &#039;mobile&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-pressed="device === &#039;mobile&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="device = &#039;mobile&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg data-slot="icon" aria-hidden="true" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="20" x="5" y="2" rx="2" ry="2"/>
  <path d="M12 18h.01"/>
</svg></button>
                            </div>
                        </div>
                        <div class="flex justify-center rounded-card border border-border bg-secondary p-3">
                            <div class="flex w-full flex-col overflow-hidden rounded-card border border-border bg-card" x-bind:data-device="device" x-bind:class="device === 'mobile' ? 'max-w-[375px]' : 'max-w-[680px]'">
                                <div class="flex flex-col gap-0.5 border-b border-border px-4 py-3 text-body-sm">
                                    <span class="truncate text-label text-foreground" x-text="fill(draft.subject) || '—'"></span>
                                    <template x-if="sender">
                                        <span class="flex flex-wrap items-center gap-x-1 text-muted-foreground">
                                            From: <span class="text-foreground" x-text="sender.name"></span> <bdi dir="ltr" x-text="'<' + sender.email + '>'"></bdi>
                                        </span>
                                    </template>
                                    <template x-if="recipient">
                                        <span class="flex flex-wrap items-center gap-x-1 text-muted-foreground">
                                            To: <bdi dir="ltr" x-text="recipient"></bdi>
                                        </span>
                                    </template>
                                </div>
                                <iframe title="Email preview" sandbox="" x-bind:srcdoc="srcdocOf(draft)" class="h-[520px] w-full border-0 bg-secondary"></iframe>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </template>
    <template x-if="! draft">
        <div data-slot="email-template-gallery" class="flex min-w-0 flex-col gap-4">
            <div class="flex flex-wrap items-center gap-2">
                <div class="relative min-w-48 flex-1 sm:max-w-sm">
                    <svg data-slot="icon" aria-hidden="true" class="pointer-events-none absolute inset-y-0 start-3 my-auto size-4 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m21 21-4.34-4.34"/>
  <circle cx="11" cy="11" r="8"/>
</svg>                    <input data-slot="input" type="search"
                    x-model="query" aria-label="Search templates…" placeholder="Search templates…" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control ps-9">
                </div>
                <template x-if="canSave">
                    <button data-slot="button"
     type="button"                         x-on:click="create()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)] ms-auto">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
  <path d="M12 5v14"/>
</svg>                        New template</button>
                </template>
            </div>
            <div x-show="present.length > 1" style="display: none" role="tablist" aria-label="Category" class="flex flex-wrap gap-1">
                <button data-slot="button"
     type="button"                         role="tab" x-bind:variant="category === &#039;all&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="category === &#039;all&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="category = &#039;all&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        All</button>
                                    <button data-slot="button"
     type="button"                         role="tab" x-show="present.includes(&#039;welcome&#039;)" style="display: none" x-bind:variant="category === &#039;welcome&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="category === &#039;welcome&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="category = &#039;welcome&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Welcome</button>
                                    <button data-slot="button"
     type="button"                         role="tab" x-show="present.includes(&#039;transactional&#039;)" style="display: none" x-bind:variant="category === &#039;transactional&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="category === &#039;transactional&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="category = &#039;transactional&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Transactional</button>
                                    <button data-slot="button"
     type="button"                         role="tab" x-show="present.includes(&#039;marketing&#039;)" style="display: none" x-bind:variant="category === &#039;marketing&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="category === &#039;marketing&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="category = &#039;marketing&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Marketing</button>
                                    <button data-slot="button"
     type="button"                         role="tab" x-show="present.includes(&#039;notification&#039;)" style="display: none" x-bind:variant="category === &#039;notification&#039; ? &#039;secondary&#039; : &#039;ghost&#039;" x-bind:aria-selected="category === &#039;notification&#039; ? &#039;true&#039; : &#039;false&#039;" x-on:click="category = &#039;notification&#039;" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        Notification</button>
                            </div>
            <template x-if="error"><div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="error"></span></div>
            </div>
    </div>
</template>
            <div data-slot="empty-state" style="display: none;" x-show="templates.length === 0" class="flex flex-col items-center justify-center gap-3 border border-dashed border-border px-6 py-12 text-center rounded-card">
            <span data-slot="state-icon" class="inline-flex size-10 items-center justify-center rounded-control border border-border bg-card [&amp;_svg]:size-5 text-muted-foreground">
            <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"/>
  <rect x="2" y="4" width="20" height="16" rx="2"/>
</svg>        </span>
        <div class="flex max-w-sm flex-col gap-1">
        <p class="text-label text-foreground">No templates yet</p>
                    <p class="text-body-sm text-muted-foreground">Create a template to reuse the same message everywhere.</p>
            </div>
    </div>
            <div data-slot="empty-state" style="display: none;" x-show="templates.length &gt; 0 &amp;&amp; shown.length === 0" class="flex flex-col items-center justify-center gap-3 border border-dashed border-border px-6 py-12 text-center rounded-card">
            <span data-slot="state-icon" class="inline-flex size-10 items-center justify-center rounded-control border border-border bg-card [&amp;_svg]:size-5 text-muted-foreground">
            <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7"/>
  <rect x="2" y="4" width="20" height="16" rx="2"/>
</svg>        </span>
        <div class="flex max-w-sm flex-col gap-1">
        <p class="text-label text-foreground">No templates match</p>
                    <p class="text-body-sm text-muted-foreground">Try another search or category.</p>
            </div>
    </div>
            <ul aria-label="Email templates" class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-4" x-show="shown.length > 0" style="display: none">
                <template x-for="tpl in shown" :key="tpl.id">
                    <li class="min-w-0">
                        <div data-slot="card" class="group flex h-full flex-col gap-0 overflow-hidden rounded-card border border-border bg-card p-0 text-card-foreground">
                            <button type="button" x-bind:aria-labelledby="'nq-et-000001-' + tpl.id" x-on:click="edit(tpl)"
                                class="flex flex-col text-start outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus">
                                <div dir="ltr" aria-hidden="true" class="relative flex h-44 justify-center overflow-hidden border-b border-border bg-secondary" x-bind:data-name="tpl.name" x-bind:title="fmt('thumbnail', { name: tpl.name })">
                                    <iframe tabindex="-1" sandbox="" x-bind:srcdoc="srcdocOf(tpl)" class="pointer-events-none absolute top-0 h-[440px] w-[600px] shrink-0 origin-top scale-[0.4] border-0" style="inset-inline-start: 50%; margin-inline-start: -300px" title=""></iframe>
                                </div>
                                <span class="flex flex-col gap-1.5 p-3">
                                    <span x-bind:id="'nq-et-000001-' + tpl.id" class="truncate text-label text-foreground" x-text="tpl.name || labels.untitled"></span>
                                    <span class="truncate text-body-sm text-muted-foreground" x-text="fill(tpl.subject)"></span>
                                    <span class="flex flex-wrap items-center gap-1.5">
                                        <template x-if="tpl.status === 'active'"><span data-slot="status" data-tone="success" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>        <span class="truncate"  title="Active" >Active</span>
</span>
</template>
                                        <template x-if="tpl.status !== 'active'"><span data-slot="status" data-tone="neutral" class="inline-flex min-w-0 items-center gap-1.5 text-body-sm text-foreground">
            <svg aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
</svg>        <span class="truncate"  title="Draft" >Draft</span>
</span>
</template>
                                        <span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border text-muted-foreground"><span x-text="labels.categories[tpl.category]"></span></span>
                                    </span>
                                </span>
                            </button>
                            <div class="mt-auto flex items-center justify-between gap-2 border-t border-border px-3 py-2">
                                <span x-show="tpl.updatedAt != null" class="truncate text-caption text-muted-foreground">Updated <span x-text="dateOf(tpl.updatedAt)"></span></span>
                                <span x-show="tpl.updatedAt == null"></span>
                                <span class="flex shrink-0 items-center">
                                    <template x-if="canDuplicate">
                                        <button data-slot="button"
     type="button"                         x-bind:aria-label="labels.duplicate + &#039;: &#039; + tpl.name" x-bind:disabled="busy ? &#039;&#039; : null" x-on:click="duplicate(tpl)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg></button>
                                    </template>
                                    <template x-if="canDelete">
                                        <button data-slot="button"
     type="button"                         x-bind:aria-label="labels.delete + &#039;: &#039; + tpl.name" x-bind:disabled="busy ? &#039;&#039; : null" x-on:click="askDelete(tpl)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg data-slot="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M10 11v6"/>
  <path d="M14 11v6"/>
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
  <path d="M3 6h18"/>
  <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg></button>
                                    </template>
                                </span>
                            </div>
                        </div>
                    </li>
                </template>
            </ul>
        </div>
    </template>
    <div data-slot="dialog" x-data="nqDialog(false)" x-modelable="open" x-id="['nq-dialog']" x-model="deleteOpen" class="contents">
    <template x-teleport="body">
    <div data-slot="dialog-portal">
        <div data-slot="dialog-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
        <div data-slot="dialog-content" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] max-w-lg gap-4 rounded-floating border border-border bg-popover p-6 text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
            <div data-slot="dialog-header" class="flex flex-col gap-1.5 pe-8 text-start"><h2 data-slot="dialog-title" :id="$id('nq-dialog', 'title')" class="text-h3 text-foreground"><span x-text="fmt('deleteConfirm', { name: pendingDelete ? (pendingDelete.name || labels.untitled) : '' })"></span></h2>
                <p data-slot="dialog-description" :id="$id('nq-dialog', 'description')" class="text-body-sm text-muted-foreground">This cannot be undone.</p></div>
            <div data-slot="dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button data-slot="button"
     type="button"                         x-on:click="deleteOpen = false" x-bind:disabled="busy ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
                <button data-slot="button"
     type="button"                         x-on:click="confirmDelete()" x-bind:aria-busy="busy ? &#039;true&#039; : null" x-bind:disabled="busy ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground hover:bg-[color-mix(in_oklab,var(--nq-danger-solid)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <span x-show="busy" style="display: none"><svg data-slot="spinner" aria-hidden="true" class="size-4 animate-spin motion-reduce:animate-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg></span>
                    Delete</button></div>
                            <button type="button" data-slot="dialog-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                    </div>
    </div>
</template>
</div>
    <div data-slot="dialog" x-data="nqDialog(false)" x-modelable="open" x-id="['nq-dialog']" x-model="testOpen" class="contents">
    <template x-teleport="body">
    <div data-slot="dialog-portal">
        <div data-slot="dialog-backdrop" x-nq-presence="open" x-on:click="close()" class="fixed inset-0 z-50 bg-nq-fg/15 dark:bg-nq-bg/60 transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"></div>
        <div data-slot="dialog-content" x-bind="popup" x-nq-presence="open" x-trap.noscroll="open"
            class="fixed inset-0 z-50 m-auto grid h-fit w-[calc(100%-2rem)] max-w-lg gap-4 rounded-floating border border-border bg-popover p-6 text-popover-foreground outline-none max-h-[calc(100dvh-2rem)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0">
            <form novalidate class="flex flex-col gap-4" x-on:submit.prevent="sendTest()">
                <div data-slot="dialog-header" class="flex flex-col gap-1.5 pe-8 text-start"><h2 data-slot="dialog-title" :id="$id('nq-dialog', 'title')" class="text-h3 text-foreground">Send a test email</h2>
                    <p data-slot="dialog-description" :id="$id('nq-dialog', 'description')" class="text-body-sm text-muted-foreground">Sends this template with the sample values to one address.</p></div>
                <div data-slot="field" x-data="nqField(false)" x-modelable="invalid" x-id="['nq-field']"
         data-valid     class="flex flex-col gap-1.5">
    <label data-slot="field-label"  class="text-label text-foreground data-disabled:opacity-50">Email address</label>
                    <input data-slot="input" type="email"
     dir="ltr"                 x-model="testEmail" autocomplete="email" class="w-full min-w-0 rounded-control border border-input bg-card px-3 text-body text-foreground min-h-[var(--nq-touch-min,0px)] transition-colors duration-150 ease-nq outline-none placeholder:text-muted-foreground focus-visible:border-nq-focus focus-visible:outline-1 focus-visible:outline-nq-focus data-invalid:border-nq-danger aria-invalid:border-nq-danger disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:text-[16px] h-control text-start">
</div>
                <template x-if="testMessage && testMessage.tone === 'danger'"><div data-slot="alert" data-tone="danger" role="alert"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-danger/30 bg-nq-danger-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-danger-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m15 9-6 6"/>
  <path d="m9 9 6 6"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="testMessage.text"></span></div>
            </div>
    </div>
</template>
                <template x-if="testMessage && testMessage.tone === 'success'"><div data-slot="alert" data-tone="success" role="status"
        class="relative grid grid-cols-[auto_1fr_auto] items-start gap-x-3 rounded-card border p-3 text-start border-nq-success/30 bg-nq-success-soft">
    <svg aria-hidden="true" data-slot="alert-icon" class="mt-0.5 size-4 text-nq-success-text" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="10"/>
  <path d="m9 12 2 2 4-4"/>
</svg>    <div data-slot="alert-body" class="flex min-w-0 flex-col gap-0.5">
                            <div data-slot="alert-description" class="text-body-sm text-foreground"><span x-text="testMessage.text"></span></div>
            </div>
    </div>
</template>
                <div data-slot="dialog-footer" class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"><button data-slot="button"
     type="button"                         x-on:click="testOpen = false" x-bind:disabled="testPending ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control px-[var(--nq-control-pad)]">
        Cancel</button>
                    <button data-slot="button"
     type="submit"                         x-bind:aria-busy="testPending ? &#039;true&#039; : null" x-bind:disabled="testPending ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-[color-mix(in_oklab,var(--nq-action)_88%,var(--nq-fg))] h-control px-[var(--nq-control-pad)]">
        <svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z"/>
  <path d="m21.854 2.147-10.94 10.939"/>
</svg>                        Send</button></div>
            </form>
                            <button type="button" data-slot="dialog-close" x-on:click="close()" aria-label="Close"
                    class="absolute end-3 top-3 inline-flex size-8 items-center justify-center rounded-control text-muted-foreground transition-colors duration-150 hover:bg-nq-hover hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus [&_svg]:size-4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M18 6 6 18"/>
  <path d="m6 6 12 12"/>
</svg></button>
                    </div>
    </div>
</template>
</div>
</div>
```
