Solution · Rich text editor

Rich text that reads and writes Markdown, and travels to any chat.

An owned editor with a closed vocabulary: load and save Markdown, paste HTML from any page without its scripts or unsafe links, set block types from a menu, add emoji, and export the result for Slack, Discord, Teams and five more formats with every loss listed. The demo runs on fixture documents; nothing is saved or sent.

Status: in development

Write, paste and send

Try this

  • Pick “Support macro”, then open the text style menu and turn a step into a heading; undo puts it back.
  • Press “Paste the HTML”: the safe link arrives, the javascript: link stays as text, the script is gone.
  • Type :tada: or open the emoji button, then read the document as Markdown below.
  • Export the note for Slack, Discord or Telegram and read what each format changes.

Use Reset to return to the starting example.

Northwind 2.4 release notes

Released 12 November 2026 for the Northwind pilot team.

New

  • Saved views in the dashboard

  • CSV export for every table

  • Dark mode for the mobile app

Fixed

  1. Search keeps your filters

  2. Dates show in your time zone

Thanks to everyone who reported the filter bug 🎉

Questions? Reply in #northwind-pilot.

Ctrl+B bold, Ctrl+I italic, Ctrl+K link, Ctrl+Z undo. Tab indents list items. Press Escape, then Tab, to leave the editor.

Try the text style menu, the emoji button, or type a shortcode such as :tada:. Press Mod+Alt+Shift+V to paste your clipboard as Markdown.

Markdown in and out

The document as Markdown

## Northwind 2.4 release notes

**Released** 12 November 2026 for the *Northwind* pilot team.

### New

- [x] Saved views in the [dashboard](https://example.com/docs/views)
- [x] CSV export for every table
- [ ] Dark mode for the mobile app

### Fixed

1. Search keeps your filters
2. Dates show in your time zone

> Thanks to everyone who reported the filter bug 🎉

Questions? Reply in #northwind-pilot.

Try pasting this HTML

A fictional wiki page on the clipboard: a safe link, a javascript: link, a click handler and a script.

<p>From the team wiki:</p><h2>Q4 planning</h2><p onclick="steal()">Read the <a href="https://example.com/plan">plan</a> and <a href="javascript:alert('hi')">this link</a> before <b>Friday</b>.</p><script>steal()</script><ul><li>Budget review</li><li>Hiring</li></ul>

Export for chat

Format

6 changes in this format

  • Checklist items are written with ☑ and ☐ marks. (×3)
  • Headings are unsupported by this dialect. (×3)

If the interactive demo cannot load, use the static preview above.

Write once, keep the Markdown, send it anywhere.

Load and save Markdown without losing the document.

Release notes, support macros and meeting notes load from Markdown and save back to the same Markdown. Raw HTML in the source stays literal text, and a link the URL policy refuses keeps its text.

  • Edit
  • Markdown

in development

Paste from any page, safely.

Copy a section of a web page and paste it: headings, lists, marks and safe links arrive; scripts, click handlers and javascript: links do not. Plain text stays plain unless you choose Paste as Markdown.

  • Safe paste

in development

Send the same note to every chat.

Export the document for Slack, Discord, WhatsApp, Telegram, Teams, Google Chat, Mattermost or plain text. Each format lists what it cannot carry, and @everyone or @here never becomes a ping.

  • Chat dialect export

From Markdown in to a message out.

  1. Read Markdown

    Stored text, agent output or a file is read by the editor codec at the untrusted tier: raw HTML stays literal text, every link passes the URL policy.

    Package: @texuli/editor

  2. Edit the closed model

    Headings, lists, todos, quotes, code and five marks, changed only through transforms; the block menu and emoji are one undo step each.

    Package: @texuli/core

  3. Paste safely

    Clipboard HTML is read without a DOM by the shared HTML reader: scripts, handlers and refused links are dropped before anything reaches the document.

    Package: @texuli/core

  4. Write it back

    Save the document as Markdown that reads back to the same document.

    Package: @texuli/editor

  5. Send it to a chat

    Write Slack, Discord, WhatsApp, Telegram, Teams, Google Chat, Mattermost or plain text, with what each format cannot carry listed for the user.

    Package: @texuli/core

Rich text editor is built from @texuli/editor, texuli and @texuli/core, on @texuli/tokens, @texuli/primitives, @texuli/emojis and @texuli/spec.

Solution

Rich text editor

Packages

  • L4@texuli/editorEditor surface, toolbar, controller, Markdown codec and paste; on npm, with the block menu, emoji, chat export and HTML paste through @texuli/core/html released in 0.18.0.Subpaths: . · ./editor · ./markdown · ./paste · ./toolbar · ./block-menu · ./emoji · ./export
  • L3texuliThe emoji picker the editor opens (released in 0.18.0).Subpaths: ./emoji
  • L0@texuli/coreFramework-free document model and Markdown codec; the HTML reader and chat dialect writers (subpaths released in 0.18.0).Subpaths: ./editor-model · ./editor-markdown · ./html · ./markdown-dialects

Built on

  • L2@texuli/tokens (Theme tokens)
  • L1@texuli/primitives (Headless primitives)
  • L0@texuli/emojis (Unicode emoji catalog and engine)
  • L0@texuli/spec (Blueprint contract)

Build with Rich text editor

Start with the editor

Markdown in and out

<script lang="ts">
import { RichTextEditor } from '@texuli/editor/editor';
import { documentToMarkdown, markdownToDocument } from '@texuli/editor/markdown';

// Untrusted by default: raw HTML stays text, every link passes the URL policy.
const { doc } = markdownToDocument('# Release notes\n\n- [x] Saved views');
let markdown = $state(documentToMarkdown(doc));
</script>

<RichTextEditor value={doc} label="Release notes" onChange={(next) => (markdown = documentToMarkdown(next))} />

Block menu, emoji and chat export (in development, released in 0.18.0)

<script lang="ts">
import { getEmoji } from '@texuli/emojis/catalog';
import { EditorController } from '@texuli/editor/controller';
import { RichTextEditor } from '@texuli/editor/editor';
import { DialectExport } from '@texuli/editor/export';

const controller = new EditorController({
  shortcodes: (name) => getEmoji(`:${name}:`)?.unicode,
});
</script>

<RichTextEditor {controller} blockMenu emoji label="Note" />
<DialectExport {controller} dialect="slack" />

Verified by tests/e2e/site-solution-editor.spec.ts

Keep building

For agents

An agent writes Markdown; the editor reads it at the untrusted tier, so raw HTML stays text and refused links keep only their text. The same text converts to a chat dialect, or renders to safe HTML, through the md.convert and md.render MCP operations. This demo uses fixture documents and sends nothing.

  • @texuli/editor@texuli/editor: In development

    Editor surface, toolbar, controller, Markdown codec and paste; published on npm.

  • HTML paste through @texuli/core/html, Paste as Markdown, block menu, emoji and chat exportHTML paste through @texuli/core/html, Paste as Markdown, block menu, emoji and chat export: Planned

    Released in 0.18.0 (pre-1.0).

Platforms

  • Web

    Web: In development

    Svelte 5 packages published on npm (pre-1.0).

  • Compose Desktop

    Compose Desktop: Planned

    Kotlin Multiplatform renderer on the JVM, built in this repository.

  • Android

    Android: Planned

    Native renderer target.

  • iOS

    iOS: Planned

    Native renderer target.

How the native renderer works

Start building with Rich text editor

Owned rich text with Markdown in and out, safe paste and export to any chat.

A low horizon of stacked cubes rising to one glowing pearl cube under a wide open skyA low horizon of stacked cubes rising to one glowing pearl cube under a wide open sky