In-Browser-Bearbeitung mit contentEditable
Contenteditable im Browser: Inline-Textbearbeitung aktivieren, input-Ereignisse erfassen, execCommand-Grenzen beachten und XSS verhindern.
Jedes HTML-Element wird direkt bearbeitbar, sobald Sie das Attribut contenteditable hinzufügen – ohne Formularsteuerelemente, ohne Bibliotheken, ohne Abhängigkeiten.
Wer schon einmal ein vollständiges Formular gebaut hat, nur damit ein Benutzer eine einzige Überschrift umbenennen kann, dem wird das beim ersten Ausprobieren wie ein Cheat-Code vorkommen.
Der Browser verwandelt das Element in einen Bearbeitungshost, setzt den Cursor und ermöglicht es dem Benutzer, direkt in das gerenderte DOM zu tippen. Damit ist contenteditable der schnellste Weg, eine bearbeitbare Überschrift, ein Klick-zum-Bearbeiten-Feld oder einen einfachen Notizbereich bereitzustellen. Das Attribut hat jedoch auch seine Tücken, die in der Attributreferenz nicht erwähnt werden: Es gibt kein natives change-Event, das erzeugte Markup variiert browserübergreifend, die alte Formatierungs-API ist veraltet, und das Zurückrendern des Ergebnisses an andere Benutzer ist ein klassischer XSS-Angriffsvektor. Dieser Artikel erklärt, wie man contenteditable aktiviert, Bearbeitungen korrekt erfasst und persistiert, mit diesen Sonderfällen umgeht und wann man besser auf eine Alternative zurückgreift.
Wichtige Erkenntnisse
- Das Attribut
contenteditableakzeptiert drei Werte:true(oder ein leerer String) macht ein Element bearbeitbar,falsedeaktiviert es, undplaintext-onlyermöglicht die Bearbeitung von reinem Text, während Rich-Text-Formatierungen entfernt werden. contentEditablehat kein nativeschange-Event. Verwenden Sie stattdessen dasinput-Event, das bei jeder Änderung am Bearbeitungshost ausgelöst wird.document.execCommand()für Fett-, Kursivschrift und Links ist veraltet und nicht standardisiert; verwenden Sie für echten Rich Text die Selection- und Range-APIs zusammen mitbeforeinput/inputoder eine dedizierte Editor-Bibliothek.- Schreiben Sie vom Benutzer eingegebenes
contenteditable-HTML niemals ohne Bereinigung zurück in die Seite – verwenden Sie DOMPurify oder, wo verfügbar, die browsereigenesetHTML()-Methode mit einem DOMPurify-Fallback. contenteditable="plaintext-only"ist inzwischen browserübergreifend verfügbar, nachdem es in Firefox 136 (März 2025) neben der bereits länger bestehenden Unterstützung in Chromium und WebKit eingeführt wurde.
Aktivierung: das contenteditable-Attribut
Das globale Attribut contenteditable akzeptiert drei Werte, und die richtige Wahl ist bereits die halbe Miete. true (oder ein leerer String) macht ein Element bearbeitbar; false deaktiviert es; und plaintext-only macht den reinen Text bearbeitbar und deaktiviert gleichzeitig die Rich-Text-Formatierung. Laut der MDN-Referenz zu contenteditable handelt es sich um ein enumeriertes Attribut, kein boolesches: Ein fehlender oder ungültiger Wert erbt die Bearbeitbarkeit vom übergeordneten Element.
Die Einzeiler-Variante:
<h1 contenteditable="true">Edit this heading</h1>
Für ein reines Textfeld (ein umbenennbarer Titel, eine Tag-Eingabe, eine einzeilige Notiz) empfiehlt sich plaintext-only. Es blockiert eingefügte Rich-Text-Formatierungen direkt an der Quelle: Inhalte, die in ein Element mit contenteditable="true" eingefügt werden, behalten ihre gesamte Formatierung, während Inhalte, die in contenteditable="plaintext-only" eingefügt werden, von jeglicher Formatierung befreit werden.
Die Bearbeitung lässt sich über JavaScript mithilfe der Eigenschaft contentEditable (in camelCase) umschalten:
const el = document.querySelector('#note');
el.contentEditable = 'plaintext-only'; // or 'true' / 'false'
Wie erfasst und persistiert man contenteditable-Bearbeitungen?
Discover how at OpenReplay.com.
contentEditable hat kein natives change-Event. Um Bearbeitungen zu erfassen, lauschen Sie auf das input-Event, das bei jeder Änderung am Bearbeitungshost ausgelöst wird. Dies ist der häufigste Fehler in älteren Tutorials, die auf keypress oder keyup zurückgreifen und dabei Einfügevorgänge, Drag-and-Drop und IME-Eingaben übersehen. Lesen Sie element.innerHTML, wenn Sie die Formatierung erhalten möchten, oder element.textContent, wenn Sie reinen Text benötigen, und persistieren und stellen Sie den Inhalt beim Laden wieder her.
const el = document.querySelector('#note');
// Restore on load
el.textContent = localStorage.getItem('note') ?? '';
// Debounced persistence on every edit
let t;
el.addEventListener('input', () => {
clearTimeout(t);
t = setTimeout(() => {
localStorage.setItem('note', el.textContent);
// or: fetch('/api/note', { method: 'POST', body: el.textContent })
}, 400);
});
Ersetzen Sie textContent durch innerHTML, wenn Sie Rich-Text-Markup speichern möchten – lesen Sie jedoch zuerst den Abschnitt zur Sicherheit, da diese Entscheidung ein Notizfeld in eine Angriffsfläche verwandeln kann. Für eine feinere Kontrolle wird das beforeinput-Event ausgelöst, bevor das DOM verändert wird, und ermöglicht es Ihnen, eine Bearbeitung zu prüfen oder abzubrechen; es gilt sowohl für contenteditable-Elemente als auch für jedes Element im designMode.
Die Fallstricke
Hier zeigt sich, warum contenteditable seinen Ruf hat. In der Produktion treten drei Probleme auf.
Unordentliches, inkonsistentes Markup. Browser sind sich uneinig darüber, welches HTML ein contenteditable-Bereich erzeugt, sodass die gespeicherte Ausgabe selten so sauber ist, wie man es erwartet. Wie Scott O’Hara dokumentiert hat, hat Safari historisch gesehen Zeilenumbrüche in <div>-Elemente eingebettet, während Firefox <br>-Elemente einfügt; <div> ist zudem kein gültiges Kindelement von <p>, was zu Darstellungsproblemen führt, wenn ein Absatz bearbeitbar gemacht wurde. Ein häufiges Problem in der Produktion ist, dass ein Benutzer Inhalte aus Word oder Google Docs einfügt und dabei einen Wust aus <span>-Elementen und Inline-Styles mitbringt. Session-Replays dieser Bearbeitungssitzungen sind eine Möglichkeit, die Entstehung dieser fehlerhaften Ausgabe direkt zu beobachten, anstatt sie aus einer beschädigten Datenbankzeile rekonstruieren zu müssen. Die pragmatische Lösung besteht darin, plaintext-only zu bevorzugen oder beim input-Event bzw. beim Einfügen zu bereinigen.
execCommand ist veraltet. document.execCommand(), das lange für Fett-, Kursivschrift und Link-Formatierungen verwendet wurde, ist laut MDN inzwischen sowohl veraltet als auch nicht standardisiert. Bauen Sie daher keine neuen Rich-Text-Funktionen darauf auf. Es überlebt in Legacy-Code, weil kein vollständiger Ersatz existiert. MDN weist darauf hin, dass es als einzige Methode den Undo-Puffer des Browsers erhält. Für neue Entwicklungen greifen Sie auf die Selection- und Range-APIs in Kombination mit beforeinput/input zurück. Seien Sie dabei ehrlich über den Aufwand: Das sind Low-Level-Primitive, kein direkter Ersatz, und das Verhalten von Range unterscheidet sich browserübergreifend. Für alles Nicht-Triviale empfiehlt sich ein spezialisiertes Editor-Framework.
XSS. Rendern Sie vom Benutzer eingegebenes contenteditable-HTML niemals ohne vorherige Bereinigung an andere Benutzer zurück. Ein unsanitierter innerHTML-Schreibvorgang ist ein direkter Injektionsvektor. Bereinigen Sie mit DOMPurify (aktiv gepflegt, aktuelle Version 3.x) oder verwenden Sie, wo verfügbar, die native Sanitizer-API des Browsers mit einem DOMPurify-Fallback:
function safeRender(el, html) {
if ('setHTML' in Element.prototype) {
el.setHTML(html); // native, strips scripts/handlers
} else {
el.innerHTML = DOMPurify.sanitize(html);
}
}
Der native Weg ist wirklich neu. Firefox 148, veröffentlicht am 24. Februar 2026, hat Unterstützung für die HTML Sanitizer API eingeführt, einschließlich Methoden wie setHTML(), die HTML vor dem Einfügen in das DOM bereinigt, um das XSS-Risiko zu reduzieren. Chrome und Edge sind gefolgt, aber setHTML() ist noch nicht als Baseline eingestuft, daher sollte der Fallback beibehalten werden. OpenReplay’s erster Blick auf die HTML Sanitizer API behandelt die technischen Details ausführlich.
Barrierefreiheit
Ein bearbeitbarer Bereich muss sich wie ein echtes Steuerelement verhalten. Fügen Sie eine sichtbare :focus-Formatierung hinzu, damit Tastaturbenutzer erkennen können, wo sich der Cursor befindet, und beschriften Sie den Bereich. contenteditable-Elemente haben keinen impliziten barrierefreien Namen; fügen Sie daher ein aria-label oder ein zugehöriges Label hinzu:
[contenteditable]:focus {
outline: 2px solid #2563eb;
outline-offset: 2px;
}
<div contenteditable="plaintext-only" aria-label="Note body" role="textbox"></div>
Bearbeitbare Elemente sind fokussierbar und nehmen an der sequenziellen Tastaturnavigation teil, obwohl verschachtelte bearbeitbare Elemente standardmäßig nicht zur Tab-Reihenfolge hinzugefügt werden. Verwalten Sie den Fokus, wenn sich Ihre Benutzeroberfläche ändert: Wenn eine Schaltfläche nach der Aktivierung durch den Benutzer verschwindet (z. B. ein Undo-Steuerelement, das zu Redo wechselt), verschieben Sie den Fokus mit .focus() zurück auf ein sichtbares Element, damit Tastaturbenutzer nicht den Fokus verlieren – ein Punkt, den Scott O’Hara in seiner Undo/Redo-Implementierung hervorhebt.
Wann sollte man contenteditable verwenden und wann nicht?
Verwenden Sie contenteditable für einfache Inline-Bearbeitungen: eine bearbeitbare Überschrift, ein Klick-zum-Bearbeiten-Feld, ein Live-Code/Vorschau-Spielfeld. Greifen Sie auf ein einfaches Formularsteuerelement zurück, wenn Sie zuverlässige, vorhersehbare Eingaben benötigen, und auf ein spezialisiertes Editor-Framework, wenn Sie strukturierten Rich Text mit sauberer Ausgabe benötigen.
| Anforderung | Bestes Werkzeug |
|---|---|
| Ein- oder mehrzeiliger reiner Text, Formularübermittlung | <input> / <textarea> |
| Inline-Bearbeitung von angezeigtem Inhalt, reiner Text | contenteditable="plaintext-only" |
| Live-Code/Vorschau-Spielfeld im Browser | contenteditable |
| Zuverlässiger Rich Text, strukturierter/kollaborativer Inhalt | Editor-Bibliothek (ProseMirror, Lexical, Tiptap) |
Die Entscheidung hängt von der Vorhersehbarkeit der Ausgabe ab. Ein <textarea> liefert einen sauberen String und ein echtes change-Event; contenteditable liefert gerendertes HTML, dessen genaue Struktur vom Browser und dem eingefügten Inhalt abhängt. Eine ausgereifte Editor-Bibliothek existiert genau deshalb, weil die Zähmung dieser Ausgabe – normalisiertes Markup, ein Dokumentenmodell, Undo-Verlauf, Bereinigung – ein umfangreiches Problem ist, das bereits jemand gelöst hat.
Greifen Sie auf contenteditable zurück, wenn die Bearbeitungsfläche klein ist und die Ausgabe reiner Text oder temporär ist. Sobald Sie zuverlässiges, strukturiertes HTML benötigen, schränken Sie entweder die Eingabe konsequent mit plaintext-only und Bereinigung ein oder übergeben Sie die Aufgabe einem dafür entwickelten Werkzeug.
FAQs
Löst contenteditable ein change-Event aus, wenn der Benutzer die Bearbeitung abschließt?
Nein. Ein contenteditable-Element hat kein natives change-Event, weshalb ältere Tutorials, die keypress oder keyup verwenden, Einfügevorgänge, Drag-and-Drop und IME-Eingaben übersehen. Lauschen Sie stattdessen auf das input-Event, das bei jeder Änderung am Bearbeitungshost ausgelöst wird, unabhängig davon, wie die Änderung vorgenommen wurde. Wenn Sie eine Bearbeitung abfangen oder abbrechen möchten, bevor das DOM verändert wird, verwenden Sie das beforeinput-Event, das ebenfalls für contenteditable-Elemente gilt.
Sollte ich contenteditable oder ein textarea für ein mehrzeiliges Textfeld verwenden?
Verwenden Sie ein textarea für reinen Text, den Sie übermitteln oder speichern möchten, da es einen sauberen String zurückgibt und ein echtes change-Event auslöst. Greifen Sie auf contenteditable nur zurück, wenn Sie eine direkte Inline-Bearbeitung von angezeigtem Inhalt benötigen, anstatt eines separaten Formularsteuerelements. Wenn das Feld nur Text enthalten soll, ist contenteditable='plaintext-only' die beste Wahl, da es eingefügte Rich-Text-Formatierungen direkt an der Quelle entfernt und gleichzeitig die direkte Bearbeitung des gerenderten Inhalts ermöglicht.
Ist execCommand noch sicher für Fett- und Kursivschrift-Formatierungen?
Bauen Sie keine neuen Rich-Text-Funktionen auf execCommand auf; MDN kennzeichnet es als veraltet und nicht standardisiert. Es überlebt in Legacy-Code, weil kein vollständiger Ersatz existiert und es als einzige Methode den Browser-Undo-Puffer erhält. Für neue Entwicklungen verwenden Sie die Selection- und Range-APIs zusammen mit den beforeinput- und input-Events, obwohl dies Low-Level-Primitive sind, deren Range-Verhalten sich browserübergreifend unterscheidet. Für alles Nicht-Triviale empfiehlt sich eine spezialisierte Editor-Bibliothek.
Funktioniert contenteditable plaintext-only in Firefox?
Ja. Der Wert plaintext-only wurde in Firefox 136 (März 2025) eingeführt und ist damit browserübergreifend verfügbar, neben der bereits länger bestehenden Unterstützung in Chromium und WebKit. Er macht den reinen Text bearbeitbar und deaktiviert gleichzeitig die Rich-Text-Formatierung, sodass in ein plaintext-only-Element eingefügte Inhalte von jeglicher Formatierung befreit werden. Dies macht es zur saubersten Wahl für reine Textfelder, da es unordentliches eingefügtes Markup direkt an der Quelle blockiert, anstatt es nachträglich bereinigen zu müssen.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k