diff --git a/crates/engine/src/cmd/format.rs b/crates/engine/src/cmd/format.rs
--- a/crates/engine/src/cmd/format.rs
+++ b/crates/engine/src/cmd/format.rs
@@ -590,23 +590,31 @@ fn font_edits(p: &Value) -> Result<FontEdits> {
 fn color_command_value(c: &deckcraft_model::ColorRef) -> Value {
     use deckcraft_model::ColorBase;
     let mut o = serde_json::Map::new();
-    for m in &c.mods {
-        if let Some(v) = m.value() {
-            o.insert(m.xml_name().to_string(), json!(v));
-        }
+    // An ordered list, so the order of the transforms and repeated ones such as `[inv, inv]` are kept.
+    if !c.mods.is_empty() {
+        o.insert("mods".into(), json!(c.mods));
     }
+    // `#RRGGBB`, or `#RRGGBBAA` for a color that is not opaque.
+    let hex = |c: &deckcraft_color::Rgba| if c.a == 255 { format!("#{}", c.hex()) } else { format!("#{}{:02X}", c.hex(), c.a) };
     match &c.base {
-        ColorBase::Rgb { rgb } if o.is_empty() => json!(format!("#{}", rgb.hex())),
+        ColorBase::Rgb { rgb } if o.is_empty() => json!(hex(rgb)),
         ColorBase::Rgb { rgb } => {
-            o.insert("rgb".into(), json!(format!("#{}", rgb.hex())));
+            o.insert("rgb".into(), json!(hex(rgb)));
             Value::Object(o)
         }
         ColorBase::Scheme { slot } => {
             o.insert("scheme".into(), json!(slot.xml_name()));
             Value::Object(o)
         }
-        ColorBase::Preset { name } => json!(name),
-        ColorBase::System { last, .. } => json!(format!("#{}", last.hex())),
+        ColorBase::Preset { name } => {
+            o.insert("preset".into(), json!(name));
+            Value::Object(o)
+        }
+        ColorBase::System { name, last } => {
+            o.insert("system".into(), json!(name));
+            o.insert("last".into(), json!(hex(last)));
+            Value::Object(o)
+        }
     }
 }
 
@@ -1538,6 +1546,31 @@ mod tests {
         assert!(r.size.is_none() && r.fill.is_none());
     }
 
+    #[test]
+    fn font_dialog_ok_keeps_the_text_color() {
+        use deckcraft_color::Rgba;
+        let preset = ColorRef { base: ColorBase::Preset { name: "red".into() }, mods: vec![] }.with(ColorTransform::LumMod(50_000));
+        let system = ColorRef { base: ColorBase::System { name: "windowText".into(), last: Rgba::rgb(0x12, 0x34, 0x56) }, mods: vec![] }
+            .with(ColorTransform::Alpha(60_000));
+        // A transform without a value, and a color that is not opaque.
+        let inverted = ColorRef { base: ColorBase::Preset { name: "red".into() }, mods: vec![] }.with(ColorTransform::Inv);
+        let translucent = ColorRef { base: ColorBase::System { name: "windowText".into(), last: Rgba::rgba(0x12, 0x34, 0x56, 0x80) }, mods: vec![] };
+        let rgb = ColorRef::rgb(Rgba::rgba(0x12, 0x34, 0x56, 0x80));
+        // A repeated transform, and two transforms in an order that is not alphabetical.
+        let twice = inverted.clone().with(ColorTransform::Inv);
+        let ordered = ColorRef::scheme(SchemeSlot::Accent2).with(ColorTransform::LumOff(20_000)).with(ColorTransform::LumMod(50_000));
+        for color in [preset, system, inverted, translucent, rgb, twice, ordered] {
+            let mut s = shape();
+            let fill = Fill::Solid { color: color.clone() };
+            format_run(&mut s, &|r| r.fill = Some(fill.clone())).unwrap();
+            s.ui_requests.clear();
+            s.execute("format.fontDialog", &json!({})).unwrap();
+            let Some(UiRequest::Dialog { params, .. }) = s.ui_requests.last().cloned() else { panic!("dialog request") };
+            s.execute("format.fontDialog", &json!({"color": params["color"]})).unwrap();
+            assert_eq!(props(&s).fill, Some(Fill::Solid { color }), "{}", params["color"]);
+        }
+    }
+
     #[test]
     fn font_dialog_with_no_fields_asks_the_ui_to_open() {
         let mut s = shape();
diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs
--- a/crates/engine/src/cmd/mod.rs
+++ b/crates/engine/src/cmd/mod.rs
@@ -372,7 +372,8 @@ pub(crate) fn ok() -> Result<Value> {
 
 /// Parse a colour param: `"#RRGGBB"`, `"accent1"`…, or `{"scheme":"accent2","lumMod":75000}`.
 /// A preset or system colour keeps its base: `{"preset":"red"}`,
-/// `{"system":"windowText","last":"#000000"}`.
+/// `{"system":"windowText","last":"#000000"}`. `"mods"` lists transforms in order:
+/// `{"preset":"red","mods":[{"op":"inv"},{"op":"inv"}]}`.
 pub(crate) fn color_param(p: &Value, key: &str) -> Option<deckcraft_model::ColorRef> {
     let v = p.get(key)?;
     color_value(v)
@@ -412,6 +413,10 @@ pub(crate) fn color_value(v: &Value) -> Option<deckcraft_model::ColorRef> {
                     c.mods.push(t);
                 }
             }
+            // `mods` lists transforms in order, repeats included: `[{"op":"inv"},{"op":"lumMod","val":50000}]`.
+            if let Some(list) = o.get("mods") {
+                c.mods.extend(serde_json::from_value::<Vec<deckcraft_color::ColorTransform>>(list.clone()).ok()?);
+            }
             Some(c)
         }
         _ => None,
@@ -669,5 +674,9 @@ mod tests {
             assert!(color_value(&json!({"preset": name})).is_some(), "{name}");
         }
         assert!(color_value(&json!({"system": "3dDkShadow", "last": "#000000"})).is_some());
+        let listed = color_value(&json!({"preset": "red", "mods": [{"op": "inv"}, {"op": "lumOff", "val": 20_000}, {"op": "inv"}]}));
+        let mods = vec![ColorTransform::Inv, ColorTransform::LumOff(20_000), ColorTransform::Inv];
+        assert_eq!(listed, Some(ColorRef { base: ColorBase::Preset { name: "red".into() }, mods }));
+        assert_eq!(color_value(&json!({"preset": "red", "mods": [{"op": "notATransform"}]})), None);
     }
 }
diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs
--- a/crates/ui-egui/src/dialogs.rs
+++ b/crates/ui-egui/src/dialogs.rs
@@ -2896,7 +2896,7 @@ fn json_text(v: Option<&Value>, default: &str) -> String {
 fn color_label(v: &Value) -> String {
     match v {
         Value::String(s) => s.clone(),
-        Value::Object(o) => o.get("scheme").or_else(|| o.get("rgb")).and_then(Value::as_str).unwrap_or("").to_string(),
+        Value::Object(o) => ["scheme", "rgb", "preset", "last"].iter().find_map(|k| o.get(*k)).and_then(Value::as_str).unwrap_or("").to_string(),
         _ => String::new(),
     }
 }
diff --git a/crates/ui-egui/src/font_dialog_tests.rs b/crates/ui-egui/src/font_dialog_tests.rs
--- a/crates/ui-egui/src/font_dialog_tests.rs
+++ b/crates/ui-egui/src/font_dialog_tests.rs
@@ -63,3 +63,36 @@ fn command_t_opens_the_font_dialog_and_cancel_keeps_the_text() {
     assert!(h.state().dialog.is_none());
     assert_eq!(bold_of(&h), None);
 }
+
+#[test]
+fn preset_and_system_colors_show_in_the_dialog_and_ok_keeps_them() {
+    for (color, shown) in [
+        (json!({"preset": "red", "lumMod": 50000}), "red"),
+        (json!({"system": "windowText", "last": "#123456", "alpha": 60000}), "#123456"),
+        (json!({"preset": "red", "mods": [{"op": "inv"}, {"op": "inv"}]}), "red"),
+        (json!({"scheme": "accent2", "mods": [{"op": "lumOff", "val": 20000}, {"op": "lumMod", "val": 50000}]}), "accent2"),
+    ] {
+        let mut h = harness();
+        h.state_mut().session.execute("format.fontDialog", &json!({ "color": color })).unwrap();
+        let before = fill_of(&h);
+        assert!(before.is_some());
+        h.get_by_label("Font…").click();
+        h.run_steps(2);
+        let d = h.state().dialog.as_ref().expect("font dialog");
+        assert_eq!(d.fields.get("color").map(String::as_str), Some(shown));
+        h.get_by_label("OK").click_accesskit();
+        h.run_steps(1);
+        assert!(h.state().dialog.is_none(), "status {:?}", h.state().status);
+        assert_eq!(fill_of(&h), before, "{shown}");
+    }
+}
+
+fn fill_of(h: &Harness<'_, SlideApp>) -> Option<deckcraft_model::Fill> {
+    let st = h.state().session.active().unwrap();
+    st.selected_shapes()
+        .first()
+        .and_then(|sh| sh.text.as_ref())
+        .and_then(|t| t.paragraphs.first())
+        .and_then(|p| p.runs.first())
+        .and_then(|r| r.props.fill.clone())
+}
