diff --git a/web/src/components/EditableText.jsx b/web/src/components/EditableText.jsx
new file mode 100644
index 0000000..ab1611a
--- /dev/null
+++ b/web/src/components/EditableText.jsx
@@ -0,0 +1,39 @@
+import { useEffect, useState } from "react";
+
+// Kleines, editierbares Textfeld. Speichert beim Verlassen und bei Enter, aber
+// nur wenn sich etwas geändert hat – wie EditableAmount daneben, nur ohne die
+// Betragsrechnerei. Escape nimmt die Änderung zurück.
+export default function EditableText({ value, onSave, placeholder, maxLength }) {
+ const [val, setVal] = useState(value || "");
+ // Von aussen neu geladen (etwa nach dem Speichern): Stand übernehmen.
+ useEffect(() => { setVal(value || ""); }, [value]);
+
+ function commit() {
+ const neu = val.trim();
+ if (neu !== (value || "")) onSave(neu);
+ }
+
+ return (
+ setVal(e.target.value)}
+ onBlur={commit}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ commit();
+ e.target.blur();
+ }
+ if (e.key === "Escape") {
+ setVal(value || "");
+ e.target.blur();
+ }
+ }}
+ />
+ );
+}
diff --git a/web/src/finance.css b/web/src/finance.css
index 125d20b..eae5f87 100644
--- a/web/src/finance.css
+++ b/web/src/finance.css
@@ -781,7 +781,8 @@ tr.row-dup {
schrumpfen (globale .table td select/input-Regel überschreiben). */
.datatable td select,
.datatable td input[inputmode],
-.datatable td input[type="text"] { max-width: none; width: 100%; }
+.datatable td input[type="text"] { max-width: none; width: 100%;
+ margin-top: 0; padding: 5px 8px; }
.datatable td .catsel { width: 100%; }
/* ---------- Kategorie-Auswahl (auf-/zuklappbarer Baum, Ersatz für