Rich Text Editor
A fully-featured WYSIWYG editor built on TipTap/ProseMirror. Supports mentions, slash commands, tables, code blocks, task lists, AI actions, image upload, and Word import — all wired through Blazor parameters.
Installation
dotnet add package Lumeo.Editor
One-time app setup (AddLumeo(), CSS & JS) is covered in the
installation guide.
Usage
@using Lumeo <RichTextEditor />
@tiptap/core, starter-kit, extension-mention,
extension-table, extension-code-block-lowlight, extension-image, etc.) plus
lowlight (MIT) and Mammoth 1.11.0 (BSD-2-Clause) for Word import.
No @tiptap-pro/* packages — the AI actions, drag handle, and bubble menu are all
custom Lumeo code using the free MIT primitives. The AI actions invoke your own LLM via the
OnAiAction callback (OpenAI, Anthropic, Mistral, Ollama — your choice, your bill). Lumeo doesn't
ship credentials and doesn't take a cut.
When to Use
- Content authoring surfaces — blog posts, knowledge-base articles, product descriptions
- Long-form fields that benefit from headings, lists, quotes, links, and tables
- Comment or reply surfaces where users expect bold, italic, mentions, and emoji
- Document editors that need Word import, AI writing assistance, or collaborative slash commands
- Anywhere you currently use a Textarea but want structured HTML output
Minimal — Bold, Italic, Link
Standard (default) — Headings, marks, lists, link, undo/redo
Full — Standard + tables, image, code block, AI menu, Word import
None — Toolbar hidden; bubble menu and keyboard shortcuts still active
@ for users, # for tags, $ for template variables./ on a new line to open the command palette. Built-in commands: Heading 1-3, Bullet list, Numbered list, Task list, Quote, Code block, Table, Image, Divider./image). The OnImageRequested callback returns the final URL; the demo returns a placeholder after a simulated upload delay..docx file.
Conversion runs entirely in your browser via WordImporter (Mammoth ships with the
Lumeo.Editor WASM bundle), so this demo works on static hosting with no server. The default style map handles
English and German Word default styles (Title/Titel, Heading 1-6/Überschrift 1-6, Body Text/Textkörper,
List Paragraph/Listenabsatz, etc.). For server-side conversion with custom callbacks (e.g. uploading
embedded images), set OnWordImportRequested.
Markdown shortcuts work too — try '**bold**' or '# Heading'.
Read-only — content is selectable but not editable
Disabled — dimmed and non-interactive
Plain-text character limit: 500. The editor scrolls internally when content overflows 260 px.
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Ctrl / ⌘ + B | Bold |
| Ctrl / ⌘ + I | Italic |
| Ctrl / ⌘ + U | Underline |
| Ctrl / ⌘ + Shift + X | Strikethrough |
| Ctrl / ⌘ + Z | Undo |
| Ctrl / ⌘ + Shift + Z | Redo |
| Ctrl / ⌘ + Alt + 1 – 3 | Heading 1 / 2 / 3 |
| / (on a new line) | Open slash-command palette |
| @ # $ | Open the corresponding trigger dropdown |
| **text** | Bold (markdown shortcut) |
| # text (space) | Heading 1 (markdown shortcut) |
| - or * (space) | Bullet list (markdown shortcut) |
| [ ] (space) | Task list item (markdown shortcut) |
API Reference
RichTextEditor
| Property | Type | Default | Description |
|---|---|---|---|
| Value | string? | null | Current HTML content. Two-way bindable via @bind-Value. |
| ValueChanged | EventCallback<string?> | — | Fired on every document change. Carries the serialised HTML. |
| Placeholder | string | "Start writing…" | Placeholder shown when the editor is empty. |
| Toolbar | EditorToolbarPreset | Standard | Toolbar density. None hides it; Minimal = Bold/Italic/Link; Standard = headings + marks + lists + undo/redo; Full adds tables, image, code block, AI menu, Word import. |
| MinHeight | int | 180 | Minimum editor height in pixels. |
| MaxHeight | int? | null | Optional maximum height in pixels. When set, the content area scrolls internally beyond this height. |
| Disabled | bool | false | Disables editing and dims the toolbar. |
| ReadOnly | bool | false | Prevents edits but keeps content selectable (no dimming). |
| Required | bool | false | Marks the field as required; sets aria-required on the root element. |
| Invalid | bool | false | Shows a destructive ring and sets aria-invalid. Also driven automatically when nested inside a <FormField> with an error. |
| ErrorText | string? | null | Error message rendered below the editor when Invalid is true (only when not inside a FormField). |
| HelperText | string? | null | Helper text rendered below the editor when there is no error (only when not inside a FormField). |
| Label | string? | null | Inline label rendered above the editor (only when not inside a FormField). |
| Name | string? | null | HTML name attribute, useful for form identification. |
| Triggers | IReadOnlyList<EditorTrigger>? | null | One or more trigger characters (e.g. @, #, $). Each trigger provides an async ItemSource callback. |
| EnableSlashCommand | bool | true | Enables the built-in / command palette (Notion-style). Supply a custom / trigger to override the default commands. |
| EnableTables | bool | true | Enables the TipTap Table extension. |
| EnableCodeBlock | bool | true | Enables syntax-highlighted code blocks. |
| EnableImages | bool | true | Enables image upload/paste. Falls back to base64 when OnImageRequested is null. |
| EnableTaskList | bool | true | Enables checkable task-list items. |
| EnableMarkdownShortcuts | bool | true | Enables Markdown input shortcuts (e.g. **bold**, # Heading). |
| EnableAiActions | bool | false | Shows an AI button in the bubble menu when text is selected. Requires OnAiAction to be wired. |
| EnableBubbleMenu | bool | true | Shows the floating bubble menu (Bold, Italic, Link, etc.) when text is selected. |
| EnableDragHandle | bool | true | Shows the drag handle on the left of each block node for drag-and-drop reordering. |
| MaxLength | int? | null | Optional plain-text character limit enforced by the editor. |
| AllowWordImport | bool | false | Adds an Import .docx button to the Full toolbar. By default, conversion runs in-browser via WordImporter. Override with OnWordImportRequested for server-side conversion. |
| OnImageRequested | Func<EditorImageRequest, ValueTask<string?>>? | null | Async callback that receives file metadata and returns the final image URL. When null, images are inlined as base64. |
| OnAiAction | Func<string, string, ValueTask<string?>>? | null | Async callback receiving (actionId, selectedText); returns replacement HTML. Action IDs: improve, shorten, expand, fix-grammar, summarize, translate. |
| OnWordImportRequested | Func<WordImportPayload, ValueTask<string>>? | null | Optional override. Receives the picked file (filename + bytes) and returns the HTML to insert. When not set, the editor calls WordImporter.ToHtmlAsync in-browser and inserts the result. |
| ToolbarMode | RichTextToolbarMode? | null | Legacy alias for Toolbar. Values: None, Basic, Full. Use Toolbar (EditorToolbarPreset) for new code. |
| MaxWordImportBytes | long | 52428800 | Maximum file size (bytes) allowed for Word import. Default is 50 MB. |
| FormField | FormFieldContext? | null | Cascaded context from a parent FormField; drives Invalid, Required, and error display automatically. |
| Class | string? | null | Additional CSS classes applied to the root wrapper element. |
| AdditionalAttributes | Dictionary<string, object>? | null | Splatted onto the root element. Useful for data-* or ARIA attributes. |
EditorTrigger
| Property | Type | Default | Description |
|---|---|---|---|
| Char | char | — | The character that activates this trigger (e.g. '@'). |
| ItemSource | Func<string, ValueTask<IReadOnlyList<TriggerItem>>> | — | Async callback called on each keystroke with the partial query; returns matching items. |
| ItemTemplate | RenderFragment<TriggerItem>? | null | Optional custom row renderer for the dropdown list. |
| ChipClass | string? | null | CSS classes applied to the chip/pill inserted into the document when an item is selected. |
TriggerItem
| Property | Type | Default | Description |
|---|---|---|---|
| Id | string | — | Unique identifier stored in the document node's data attribute. |
| Label | string | — | Display name shown in the dropdown and rendered in the chip. |
| Subtitle | string? | null | Secondary text shown below the label in the dropdown row. |
| IconName | string? | null | Lucide icon name (e.g. "User") displayed alongside the label. |
| Payload | object? | null | Arbitrary data passed back to the consumer for custom rendering or server look-ups. |
EditorImageRequest
| Property | Type | Description |
|---|---|---|
| FileName | string | Original file name chosen by the user. |
| MimeType | string | MIME type of the selected file (e.g. image/png). |
| Size | long | File size in bytes. |
EditorToolbarPreset
| Value | Description |
|---|---|
| None | No toolbar. Bubble menu and keyboard shortcuts remain available. |
| Minimal | Bold, Italic, Link only. |
| Standard | Headings, basic marks (bold, italic, underline, strikethrough), lists, link, undo/redo. Default value. |
| Full | Standard plus tables, image, code block, AI action menu, and Word import button. |
WordImporter (server-side)
WordImporter is a static server-side helper in Lumeo.Editor
that converts a .docx stream to clean HTML using Mammoth.NET.
Call it from an API endpoint and return the result to the editor via
SetHtmlAsync.
| Member | Description |
|---|---|
| ToHtmlAsync(stream, options?) | Converts a .docx stream to WordImportResult containing Html and Warnings. |
| DefaultStyleMap | Built-in Mammoth style map covering English and German Word styles (Title, Heading 1-6, Body Text, Quote, etc.). |
| WordImportOptions.StyleMap | Custom style map appended after the default map (so custom rules win). |
| WordImportOptions.ConvertImage | Optional async callback (contentType, stream) => url to upload embedded images rather than base64-inlining them. |
ASP.NET Core endpoint example
// ASP.NET Core controller endpoint
[HttpPost("/api/upload-docx")]
public async Task<IActionResult> ImportDocx(IFormFile file)
{
using var stream = file.OpenReadStream();
var result = await WordImporter.ToHtmlAsync(stream);
return Ok(new { html = result.Html, warnings = result.Warnings });
}
// Optional: custom image upload during import
var options = new WordImportOptions
{
ConvertImage = async (contentType, imageStream) =>
{
var url = await _storage.UploadAsync(contentType, imageStream);
return url;
}
};
var result = await WordImporter.ToHtmlAsync(stream, options);