# React Rich Text Editor mit Tiptap, Markdown und Mentions

> Bau einen React Rich Text Editor auf Tiptap mit Markdown-Shortcuts, @Mentions und Bild-Uploads, der HTML, JSON oder Markdown speichert und am Handy gut läuft.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 19. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 10 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-rich-text-editor
- Englische Fassung: https://wingo-ui.com/blog/react-rich-text-editor

## Kurz gesagt

Bau deinen React Rich Text Editor auf Tiptap: Es steht unter MIT-Lizenz, läuft auf ProseMirror und bringt Markdown-Shortcuts, Mentions und ein Schema mit, das nur bekannte Nodes durchlässt. Entscheide dich für ein Speicherformat (HTML, Tiptap JSON oder Markdown), render gespeicherte Inhalte mit denselben Styles und sorg dafür, dass die Toolbar auch bei offener Smartphone-Tastatur erreichbar bleibt. Der Rich Text Editor von Wingo UI packt all das in eine Komponente mit einer format-Prop.

Du brauchst einen React Rich Text Editor für Notizen, Kommentare, Produktbeschreibungen oder ein Hilfecenter. Die erste Version steht schnell: Tiptap installieren, StarterKit dazu, eine Toolbar rendern. Dann kommen die Fragen. Was landet in der Datenbank? Wie werden eingefügte Bilder hochgeladen? Warum zoomt iOS beim Fokus in den Text? Wo bleibt die Toolbar, wenn die Smartphone-Tastatur den halben Bildschirm verdeckt? Dieser Guide beantwortet genau das, vergleicht die Editor-Frameworks und zeigt die wichtigen Props im [Rich Text Editor](https://wingo-ui.com/components/rich-text-editor) von Wingo UI, mit Code für Laden, Speichern, Mentions und Uploads.

## Welches Framework für einen React Rich Text Editor soll ich nehmen?

Nimm Tiptap, solange nichts dagegen spricht. Es verpackt ProseMirror in eine React-API, steht unter MIT-Lizenz, und seine Extensions decken ab, was Editoren in Produkten brauchen: Listen, Checklisten, Links, Bilder, Mentions und Markdown. Die Angaben in der Tabelle stammen aus der Doku und dem Repository des jeweiligen Projekts, Stand Oktober 2026.

| Option | Basis | Lizenz | Was du bekommst |
|---|---|---|---|
| [Tiptap](https://tiptap.dev/docs/editor/getting-started/install/react) | ProseMirror | MIT (Editor und Open-Source-Extensions) | Einen Headless-Editor: Toolbar und Menüs baust du selbst |
| [Lexical](https://lexical.dev/) | Eigene Engine von Meta | MIT | Ein Headless-Framework mit React-Bindings |
| [Plate](https://github.com/udecode/plate) | Slate | MIT | Ein Plugin-Framework plus UI im shadcn-Stil, kopiert über die shadcn CLI |
| [Novel](https://github.com/steven-tey/novel) | Tiptap | Apache-2.0 | Einen Editor im Stil von Notion mit KI-Autovervollständigung über OpenAI |
| [Wingo UI Rich Text Editor](https://wingo-ui.com/components/rich-text-editor) | Tiptap | Pro-Lizenz | Eine Komponente: Toolbar, Bubble Menu, Mentions, Uploads, Zähler, Smartphone-Layout |

Lexical ist eine gute Wahl, wenn du die Engine von Meta willst und es dir nichts ausmacht, mehr von der UI selbst zu bauen. Plate passt, wenn du ohnehin in Slate denkst. Tiptap selbst verkauft eine [Vorlage für einen Notion-ähnlichen Editor](https://tiptap.dev/docs/ui-components/templates/notion-like-editor) mit Slash-Menü, Umsortieren von Blöcken, KI und Echtzeit-Kollaboration. Laut Doku ist für den Produktiveinsatz mindestens der Start-Plan nötig, den die [Preisseite von Tiptap](https://tiptap.dev/pricing) mit 49 $ im Monat bei jährlicher Abrechnung oder 59 $ bei monatlicher Abrechnung angibt.

## Kann ich einen Notion-ähnlichen Editor in React bauen?

Ja, zumindest für die drei Funktionen, mit denen ein Notion Editor in React meistens anfängt: Markdown direkt in die Seite tippen, @Mentions und Bilder, die beim Einfügen hochgeladen werden. Der Rich Text Editor kann alle drei. Tipp `# ` für eine Überschrift, `- ` für eine Liste, `[] ` für eine Checkliste, `> ` für ein Zitat, drei Backticks für einen Codeblock oder `**Wort**` für Fett, und füg Markdown aus einer README ein: Es erscheint sofort formatiert.

Ein Slash-Menü und Drag-Handles für Blöcke bringt er nicht mit. Brauchst du sie, hängt die Prop `extensions` deine eigenen Tiptap-Extensions hinter die eingebauten, und das Open-Source-Utility Suggestion von Tiptap, das hier schon die Mentions antreibt, ist die übliche Grundlage für ein „/“-Menü. Willst du das volle Notion-Gefühl mit KI und Kollaboration ab Werk, kommst du mit Novel oder der kostenpflichtigen Vorlage von Tiptap schneller ans Ziel.

Probier die Shortcuts im Live-Editor unten aus und schalt dann die Output-Tabs um, um dasselbe Dokument als HTML, JSON und Markdown zu sehen.

![Tipp # und ein Leerzeichen für eine Überschrift oder [] für eine Checkliste, tipp @, um jemanden aus dem Team zu erwähnen, und schalt dann die Output-Tabs zwischen HTML, JSON und Markdown um](#figure-demo:rich-text-editor?h=640)

## Welche Props braucht ein Tiptap React Editor?

Drei für den Inhalt: `value` (oder `defaultValue`), `onValueChange` und `format`. Ein Tiptap React Editor, den du von Hand baust, braucht `useEditor`, `EditorContent`, die Liste der Extensions und `immediatelyRender: false` für Tiptap in Next.js. Diese Komponente richtet das alles für dich ein.

```bash
npx wingo-ui@latest add rich-text-editor
```

```tsx
"use client";

import { useState } from "react";
import { RichTextEditor, type RichTextValue } from "@/components/ui/rich-text-editor";

export function ProductDescription() {
  const [html, setHtml] = useState<RichTextValue>("<p>Handgefertigt in Portland aus <strong>Vollnarbenleder</strong>.</p>");

  return (
    <RichTextEditor
      label="Beschreibung"
      value={html}
      onValueChange={(next) => setHtml(next)}
      placeholder="Beschreib das Produkt…"
      characterLimit={2000}
      showCount="both"
      locale="en-US"
    />
  );
}
```

`onValueChange` feuert 150ms nach dem letzten Tastendruck (`updateDebounce`) und sofort, wenn der Editor den Fokus verliert. So bremst das Serialisieren eines langen Dokuments nie das Tippen. Ein leerer Editor meldet in den Formaten HTML und Markdown `""`. Gibst du den Wert zurück, den er gerade ausgegeben hat, bleibt der Cursor, wo er ist; ein anderer Wert, etwa ein vom Server geladenes Dokument, ersetzt den Inhalt.

Die Props, zu denen du als Nächstes greifst:

- `toolbar`: `true` zeigt `DEFAULT_TOOLBAR`, `false` blendet sie aus, während Tasten und Shortcuts weiter funktionieren, oder du übergibst die Einträge in ihrer Reihenfolge mit `"|"` zwischen den Gruppen.
- `toolbarPosition="bottom"` für ein Kommentarfeld, `variant` (`outline`, `soft`, `ghost`) und `size` (`sm`, `md`, `lg`).
- `headingLevels` (standardmäßig 1 bis 3), `characterLimit`, `minHeight` und `maxHeight`.
- `readOnly` rendert das Dokument mit denselben Styles und ohne Toolbar, Links öffnen sich in einem neuen Tab.
- `setApi` gibt dir `getHTML`, `getJSON`, `getMarkdown`, `getText`, `focus`, `clear`, `insertContent`, `isEmpty` und den Tiptap-`editor` selbst.

## Soll der Editor HTML, JSON oder Markdown speichern?

Entscheide dich für eins und bleib dabei, denn jedes Format bestimmt, wie du renderst, suchst und migrierst. Die Prop `format` legt fest, welches Format `value`, `defaultValue` und `onValueChange` sprechen.

| Speichern als | Gut für | Achte auf |
|---|---|---|
| `html` (Standard) | Webseiten, E-Mails, Server-Rendering ohne Tiptap | Vor dem Rendern bereinigen |
| `json` | Einen Editor, der als Einziger schreibt, Migrationen, eigene Renderer | Braucht zum Rendern das Schema von Tiptap |
| `markdown` | Inhalte, die Menschen oder Tools auch roh lesen: Doku, KI-Prompts, Git | Manche Marks haben keine Standard-Syntax |

Wer nach „react rich text editor markdown“ sucht, will meistens wissen, ob bei der Umwandlung hin und zurück etwas verloren geht. Über die offizielle Extension `@tiptap/markdown` werden Überschriften, Listen, Links, Code und Bilder zu Standard-Markdown und Checklisten zur GitHub-Flavored-Syntax `- [ ]`. Für Unterstreichung gibt es in Markdown keine Syntax, sie kommt als `++text++` heraus, und eine Mention als Shortcode wie `[@ id="emma-carter" label="Emma Carter"]`. Die [Markdown-Doku von Tiptap](https://tiptap.dev/docs/editor/markdown) bezeichnet die Extension als Early Release, das sich noch ändern kann. Wenn du Markdown speicherst, blend den Button für Unterstreichung über eine eigene `toolbar` aus (Mod+U unterstreicht trotzdem, rechne also hin und wieder mit `++text++`) und teste Mentions mit allem, was den Text später liest.

Für ein normales Formular-Submit legt `name` ein verstecktes Input-Feld mit dem Wert im aktuellen Format an. Eine Server Action bekommt ihn also wie jedes andere Feld:

```tsx
"use client";

import { RichTextEditor } from "@/components/ui/rich-text-editor";
import { Button } from "@/components/ui/button";
import { saveNote } from "./actions";

export function NoteForm() {
  return (
    <form action={saveNote} className="flex flex-col gap-3">
      <RichTextEditor name="body" format="markdown" label="Notiz" required toolbar={["heading", "|", "bold", "italic", "link", "|", "bulletList", "taskList"]} />
      <Button type="submit">Notiz speichern</Button>
    </form>
  );
}
```

In `saveNote` ist `formData.get("body")` der Markdown-String. Weil der Editor den Wert beim Verlassen des Felds sofort meldet, schickt auch ein Klick auf Speichern direkt nach dem Tippen den neuesten Text.

> Live-Demo (Rich Text Editor): Dieser Editor startet mit Markdown (format="markdown"): Formatier eine Zeile mit einem Shortcut und öffne dann darunter den Markdown-Tab, um die Quelle zu sehen, die er speichern würde. Probier sie unter [Rich Text Editor](https://wingo-ui.com/components/rich-text-editor) aus und installier sie mit `npx wingo-ui@latest add rich-text-editor`.

## Wie ergänze ich Mentions und Bild-Uploads?

Übergib `mentions` mit einer `items`-Funktion und `onImageUpload` mit einer Upload-Funktion. Beide dürfen async sein.

```tsx
"use client";

import {
  RichTextEditor,
  type MentionItem,
  type RichTextImageUploadOptions,
} from "@/components/ui/rich-text-editor";

async function searchPeople(query: string): Promise<MentionItem[]> {
  const response = await fetch(`/api/people?q=${encodeURIComponent(query)}`);
  return response.ok ? response.json() : [];
}

// xhr statt fetch, weil fetch keinen Upload-Fortschritt meldet
function uploadImage(file: File, { onProgress, signal }: RichTextImageUploadOptions) {
  return new Promise<string>((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const body = new FormData();
    body.append("file", file);
    xhr.open("POST", "/api/uploads");
    xhr.upload.onprogress = (event) => {
      if (event.lengthComputable) onProgress(event.loaded / event.total);
    };
    xhr.onload = () => (xhr.status < 300 ? resolve(JSON.parse(xhr.responseText).url) : reject(new Error("Upload fehlgeschlagen")));
    xhr.onerror = () => reject(new Error("Upload fehlgeschlagen"));
    signal.addEventListener("abort", () => xhr.abort());
    xhr.send(body);
  });
}

export function TicketReply() {
  return (
    <RichTextEditor
      label="Antwort"
      placeholder="Schreib eine Antwort, mit @ holst du jemanden dazu"
      mentions={{ items: searchPeople }}
      onImageUpload={uploadImage}
      imageAccept="image/png,image/jpeg,image/webp"
      maxImageSize={5 * 1024 * 1024}
    />
  );
}
```

Tipp `@`, und die Liste folgt dem Cursor. Mit den Pfeiltasten bewegst du dich, Enter oder Tab übernimmt, Escape schließt, und die Person wird zu einem Chip mit einer `data-id`. Eine async `items`-Funktion wartet 120ms zwischen den Tastendrücken, damit du den Server nicht bei jedem Buchstaben abfragst. Mit `trigger` nimmst du ein anderes Zeichen, mit `onMention` benachrichtigst du jemanden.

Bilder kommen über die Dateiauswahl in der Toolbar, per Einfügen oder per Drag and Drop. Eine abgedunkelte Vorschau mit Fortschrittsbalken hält den Platz frei, solange `uploadImage` läuft. Schlägt der Upload fehl, verschwindet der Platzhalter, und ein Screenreader sagt „The image could not be uploaded“ an. Dateien über `maxImageSize` (standardmäßig 10 MB) oder außerhalb von `imageAccept` werden schon vor dem Upload abgelehnt, und Bilder als Base64 kommen gar nicht erst ins Dokument. Ohne `onImageUpload` fragt der Bild-Button stattdessen nach einer Bildadresse. Brauchst du nur Mentions in reinem Text, speichert der schlankere [Mention Input](https://wingo-ui.com/components/mention-input) sie als Markup mit IDs; warum IDs besser sind als Namen, erklärt der [Guide zu React Mentions](https://wingo-ui.com/de/blog/react-mentions).

## Wie rendere ich gespeicherten Rich Text, ohne Tiptap zu laden?

Setz die exportierten `PROSE`-Klassen auf den Wrapper deines gespeicherten HTML. Dann sieht die Seite aus wie der Editor, ohne dass du den Editor auslieferst. Bereinige das HTML vorher auf dem Server. Das Schema des Editors verwirft unbekannte Tags, und der Link-Editor lehnt `javascript:`-Adressen über `normalizeUrl` ab, aber jeder kann beliebiges HTML an deine API schicken. Jag es also durch einen Sanitizer wie DOMPurify, bevor du es speicherst oder renderst.

```tsx
import { PROSE } from "@/components/ui/rich-text-editor";
import { toneClasses } from "@/lib/tones";
import { cn } from "@/lib/utils";

export function NoteBody({ html }: { html: string }) {
  // html wurde vor dem Speichern auf dem Server bereinigt
  return <div className={cn(PROSE, toneClasses.neutral)} dangerouslySetInnerHTML={{ __html: html }} />;
}
```

`toneClasses` setzt die Tone-Variablen, die die Chip-Klassen aus `MENTION` lesen. `PROSE` deckt Überschriften, Listen, Zitate, Code, Links und Bilder ab; Checklisten brauchen hier eigene Styles, oder du renderst die Notiz stattdessen mit `readOnly` im Editor.

Für gespeichertes Markdown nimmst du den [Markdown](https://wingo-ui.com/components/markdown)-Renderer. Er rendert GitHub Flavored Markdown (Tabellen, Checklisten, Fußnoten) im selben Look, rendert nie rohes HTML aus der Quelle und hat einen Streaming-Modus für KI-Antworten, den das [Tutorial zum Streaming von Markdown in React](https://wingo-ui.com/de/blog/react-markdown-streaming) behandelt.

> Live-Demo (Markdown): Gespeichertes Markdown, gerendert mit der Markdown-Komponente: eine Tabelle, die in ihrer eigenen Box seitwärts scrollt, wenn sie zu breit ist, eine nummerierte Liste und Inline-Code. Probier sie unter [Markdown](https://wingo-ui.com/components/markdown) aus und installier sie mit `npx wingo-ui@latest add markdown`.

## Wie verhält sich der Editor am Smartphone?

Er behandelt die Smartphone-Tastatur als Teil des Layouts. Der Text ist auf Smartphones in jeder Größe 16px groß, also zoomt iOS Safari beim Fokus nie hinein. Während du auf einem Touchscreen tippst, dockt die Toolbar über der Tastatur an, als seitwärts scrollende Leiste aus 44px großen Buttons, damit die Formatierung immer in Daumenreichweite bleibt. Unter 640px behält die obere Toolbar Rückgängig, Wiederholen, Textstil, Fett, Kursiv, Link und die beiden Listen und verschiebt den Rest in ein ⋯-Menü. Das Textstil-Menü, das ⋯-Menü und der Link-Editor öffnen sich als Bottom Sheets.

Das Bubble Menu über markiertem Text erscheint nur mit Maus oder Trackpad, denn Smartphones haben ihre eigenen Auswahlmenüs, und ein zweites würde sie verdecken.

## Lässt sich der Editor per Tastatur bedienen?

Ja. Toolbar-Buttons mit Shortcut zeigen ihn im Tooltip (⌘B am Mac, sonst Ctrl+B), und der gedrückte Zustand folgt dem Cursor. Alt+F10 springt vom Text in die Toolbar, Pfeil links und Pfeil rechts wechseln zwischen den Buttons, Pos1 und Ende springen an die Enden, und Escape bringt dich zurück in den Text. Mod+K öffnet den Link-Editor am Cursor: Enter übernimmt, eine leere Adresse entfernt den Link, und Adressen ohne Schema bekommen `https://`. Tab und Shift+Tab rücken Listeneinträge ein und aus.

Mit `label`, `description` und `error` nutzt der Editor dieselbe Field-Anatomie wie die anderen Eingabefelder, also sind `aria-describedby`, `aria-invalid` und `aria-required` schon verdrahtet. Der Zeichenzähler meldet sich nur, wenn er 90% und 100% des Limits überschreitet, nicht bei jedem Tastendruck. Alle Texte stecken in `labels` und werden über `DEFAULT_RICH_TEXT_EDITOR_LABELS` gelegt, so übersetzt du den Editor, ohne ihn zu forken. Dieselben Regeln für Felder gelten für jedes Bedienelement im [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide).

## Gibt es einen shadcn Rich Text Editor?

Nicht in shadcn/ui selbst: In der [Komponentenliste](https://ui.shadcn.com/docs/components) gibt es eine Textarea, aber keinen Editor, Stand Oktober 2026. Community-Registries füllen die Lücke. [Minimal Tiptap](https://github.com/Aslam97/minimal-tiptap) (MIT) installierst du über die shadcn CLI aus seiner Registry-URL, und es gibt HTML, JSON, Text oder Markdown aus. Plate installiert seine Editor-UI mit `npx shadcn@latest add @plate/plate-ui`. Der Rich Text Editor von Wingo UI setzt auf dasselbe Prinzip: Du kopierst den Quellcode ins Projekt. Nimm Minimal Tiptap für eine kostenlose Toolbar auf Tiptap, Plate für einen Plugin-Stack auf Slate und diesen Editor, wenn Mentions, Uploads, Zähler und die Smartphone-Toolbar schon fertig sein sollen.

## Welche typischen Stolperfallen gibt es?

- **Rendern auf dem Server.** Tiptap braucht in Next.js `immediatelyRender: false`, sonst stimmen Server- und Client-HTML nicht überein. Setz es in deinem eigenen Editor; die Komponente macht es schon.
- **Ein Rich Text Editor für ein einfaches Feld.** Eine Bio oder eine Support-Nachricht braucht selten Überschriften. Die kostenlose [Textarea](https://wingo-ui.com/components/textarea) wächst mit ihrem Inhalt und zählt Zeichen.
- **Gemischte Formate.** Wer auf einer Seite HTML und auf einer anderen Markdown speichert, macht jede spätere Migration schwerer.
- **Kommentarfelder selbst bauen.** Brauchst du Antworten, Reaktionen und einen optimistischen Composer, hat der [Comment Thread](https://wingo-ui.com/components/comment-thread) das schon.

## Wie baue ich ihn in mein Projekt ein?

Führ `npx wingo-ui@latest add rich-text-editor` aus. Die CLI kopiert den Quellcode nach `components/ui`, ergänzt die Komponenten und Hooks, die der Editor importiert, und installiert die Tiptap-Pakete, `@radix-ui/react-popover`, `motion` und `lucide-react`. Der Rich Text Editor und der Markdown-Renderer sind Pro-Komponenten ([Preise](https://wingo-ui.com/de/pricing)). Weitere Deep Dives zu einzelnen Komponenten findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components).

## Komponenten in diesem Artikel

- [Rich Text Editor](https://wingo-ui.com/components/rich-text-editor) (Pro): A React rich text editor on Tiptap with a toolbar, Markdown shortcuts, mentions, images and counts that outputs HTML, JSON or Markdown. Installation: `npx wingo-ui@latest add rich-text-editor`
- [Markdown](https://wingo-ui.com/components/markdown) (Pro): A React Markdown renderer in the library's look: scrolling tables, highlighted code, task lists and a flicker-free streaming mode for AI. Installation: `npx wingo-ui@latest add markdown`

## Häufige Fragen

### Welcher Rich Text Editor ist der beste für React?

Für die meisten Produkt-Editoren ist es Tiptap: Er steht unter MIT-Lizenz, baut auf ProseMirror auf und hat React-Bindings sowie Extensions für Markdown und Mentions. Die wichtigste Alternative ist Lexical von Meta, und Plate passt, wenn du ein Framework auf Basis von Slate mit UI im shadcn-Stil willst.

### Ist Tiptap kostenlos?

Der Tiptap-Editor und seine Open-Source-Extensions, darunter StarterKit, Mention und Markdown, stehen unter MIT-Lizenz. Die Cloud-Funktionen von Tiptap, etwa gehostete Kollaboration, sind kostenpflichtig, und die Vorlage für den Notion-ähnlichen Editor braucht für den Produktiveinsatz mindestens den Start-Plan (49 $ im Monat bei jährlicher Abrechnung, Stand Oktober 2026).

### Wie nutze ich Tiptap mit Next.js?

Leg den Editor in eine Client-Komponente und übergib useEditor die Option immediatelyRender: false, wie es der Next.js-Guide von Tiptap macht. Dann initialisiert sich der Editor nach der Hydration im Client, und es entsteht kein Hydration-Mismatch. Der Rich Text Editor von Wingo UI setzt das bereits und reserviert seine Mindesthöhe, damit beim Mounten nichts springt.

### Soll ich Rich Text als HTML, JSON oder Markdown speichern?

Speicher Tiptap JSON, wenn nur der Editor schreibt, HTML, wenn du den Inhalt vor allem auf Webseiten oder in E-Mails renderst, und Markdown, wenn Menschen oder Tools auch den Rohtext lesen. Markdown kann nicht alles abbilden: Unterstreichungen und Mentions landen als Tiptap-eigene Syntax im Text.

### Ist der Rich Text Editor von Wingo UI kostenlos?

Nein. Der Rich Text Editor und der Markdown-Renderer sind Pro-Komponenten. Die Textarea für einfache mehrzeilige Eingaben ist kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/react-rich-text-editor
