Skip to content

Richer email editor: Write and Style modes, new blocks (#169) - #171

Open
clbotdev wants to merge 18 commits into
mainfrom
t3/implement-issue-169
Open

clbotdev wants to merge 18 commits into
mainfrom
t3/implement-issue-169

Conversation

@clbotdev

@clbotdev clbotdev commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Closes #169.

Redesigns the email editor around writing, following the design attached to the issue.

What changes

Write mode

  • The subject and preview text sit above the email, with an inbox preview.
  • Text is edited paragraph by paragraph:
    • Enter starts a new paragraph or list item; Backspace joins paragraphs.
    • Markdown shortcuts (#, ##, -, 1., >) turn a paragraph into a heading, list or quote.
    • Merge tags show as chips.
    • Text blocks are still stored as one markdown block each, so there are no data changes.
  • A format bar (⌘B/I/K), a / block menu, a {{ merge-tag menu, and drag or arrow-key reordering.
  • Clicking a block opens a compact popover (for example, label and URL for a button). Its full settings are one click away in Style mode.
  • Undo and redo work across the whole document.
  • A "Before you send" checklist covers the missing subject, preview text, broken links, missing alt text and social URLs, with one-click fallbacks for merge tags that may be blank.

Style mode

  • Two independently scrolling cards: the canvas, and the Email / Block / Tags panel.
  • Desktop, mobile, sample-data and dark-inbox previews, palette presets, and typography settings.

New blocks

  • spacer, columns, social and html.
  • conditional: a section shown to contacts with or without a tag. Marketing only; it renders as Liquid and fails closed.

For packages that embed the editor

  • New EmailEditor props: subject, onSubjectChange, variables, senderName, theme and defaultMode.
  • Blocks can define quickEdit for the popover.
  • Hosts can still add their own blocks and leave out any or all of ours; component tests cover this.

API

  • The new blocks are validated on write.
  • conditional in transactional content is rejected with conditional_not_allowed.
  • Updated: REST error docs, MCP tool text, and the editor docs.

Web

  • Template and sequence-email screens use the new editor; the template title moves into the top bar.
  • A broadcast's subject is saved as its sequence title, which is what the API sends.

Testing

  • bun run test passes in every workspace (0 failures), and lint passes.
  • New tests:
    • Unit tests for text items, markdown editing, history, checklist and inline markdown.
    • happy-dom component tests for host blocks and writing behaviour.
    • API tests for validation and real Liquid rendering of conditionals.
  • Tested by hand in a browser: writing, slash menu, popovers, drag, undo, checklist fallbacks, Style previews, and the save round trip for templates and broadcasts.
  • Not tested: "Send test" and a real send, because no ESP was configured locally.

Known rough edges (not in this PR)

  • A new broadcast's subject starts as the template name.
  • Nothing has focus when the editor opens.
  • While a paragraph with a link is being edited, the raw link markdown changes how lines wrap.
  • "Your sender name" shows when no ESP is set up.

…nd new blocks (#169)

Write mode edits text paragraph by paragraph with markdown shortcuts, a
format bar, a / block menu, {{ merge tags, undo/redo and a send checklist;
Style mode previews desktop, mobile, sample data and dark inboxes beside
the palette and block settings. Adds spacer, columns, social, html and
conditional blocks, and a quickEdit hook so host blocks get the compact
popover. Hosts can still add their own blocks and leave out any of ours.
… writing (#169)

Escape closes a block's quick settings from anywhere and returns focus to
the block; tabbing on to another row closes them; Enter in a field closes
them and carries on writing below. The arrow keys now stop on buttons and
images between paragraphs, and the first field's text is selected so
typing replaces a placeholder.
Each block's popover starts on its first control; deleting a block keeps
focus in the email; the + insert menu takes focus and moves with the arrow
keys, closing on Escape or Tab back to its button; popovers close when focus
leaves the editor but stay open behind a dialog they opened, whose Escape
and Enter no longer reach them. Spacer and section options report their
pressed state, social checkboxes are named, and /conditional finds the
Section block.
… style (#169)

A text block of only headings, like a starter's title set at 26px, passed
its size and colour on to paragraphs typed after it. Those now take the
style of the nearest body text, joining it when it comes straight after.
…tor (#169)

Write mode: each row is one Tab stop named after its content, with the +
and drag handles left to the pointer and Alt+Shift+arrows moving rows
(announced to screen readers); block previews are inert so their links
can't be followed; Escape keeps focus on the paragraph; up from the first
paragraph reaches the preview text; Alt+F10 reaches the format toolbar;
the / and {{ menus are list boxes the text field points into; an edited
field resizes with the window. Style mode: Enter on a block goes to its
settings and Escape returns; Escape deselects; the arrows move between
blocks; the panel tabs follow the tab pattern; sliders and reset buttons
are named. The checklist closes on Escape or when focus leaves it.

The web editor screen asks before leaving with unsaved edits, warns on
reload, and saves on Ctrl/Cmd+S.
…#169)

The unlayered :where(.sle) reset beat Tailwind's layered utilities, so
select triggers lost their border and padding.
…too (#169)

Blocks that set their own text colour stayed dark on the dark page. Dark text
is now lightened and light backgrounds darkened, as dark-mode mail clients do;
mid-tone colours such as a button's are kept.
…urs (#169)

Starter templates give their text, buttons and dividers their own colours, so
a dark palette left them dark on dark. Choosing a palette now keeps each text
and background colour's tone between the new page and text colours, gives
buttons the accent, and lets links and dividers follow the palette.
…#169)

React Email's default quote is a #f9f9f9 box with a #ccc bar, and Write mode
copied it, so on a dark palette the quote was a white slab with pale text.
The sent quote now mixes its tint and bar from the page and text colours,
and Write mode does the same with currentColor.
…inbox (#169)

A sweep of every palette in Write, Style and dark inbox found text under
WCAG AA:
- Dark inbox lightens an accent too dark to read as a link on the dark
  page, and gives buttons text to match.
- Recoloured block text is kept at 4.5:1 against the new page (the footer
  fell just short on Warm, Forest and Plain).
- The conditional label, preview text and "Preview in inbox" fade from the
  page's text colour instead of fixed greys, and merge-tag chips lean toward
  it, so they read on dark palettes.
- Preview text wraps instead of being cut off on narrow screens.
…ards (#169)

Explains what the editor does, a quick start with state, how to render HTML
for sending, every prop, the blocks, keyboard shortcuts, and what we test
against: WCAG 2.2 AA for the editor, WAI-ARIA Authoring Practices for its
controls, and 4.5:1 text contrast in the built-in palettes. Drops the claim
that Tailwind is required: styles.css is pre-built.
# Conflicts:
#	apps/api/src/mcp/tools/templates.ts
bun run format only formats; biome ci also checks import order.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Richer email editor

2 participants