diff --git a/crates/engine/src/cmd/expand.rs b/crates/engine/src/cmd/expand.rs
--- a/crates/engine/src/cmd/expand.rs
+++ b/crates/engine/src/cmd/expand.rs
@@ -131,7 +131,11 @@ impl Expandable {
                 e.placed |= matches!(c.kind, NodeKind::PlacedDocument(_));
                 e.repeats |= matches!(c.kind, NodeKind::Repeat(_));
                 e.intertwines |= matches!(c.kind, NodeKind::Intertwine { .. });
-                e.stroke |= shape && !c.appearance.stroke_paint().is_none();
+                e.stroke |= match &c.kind {
+                    // Type's outlines take its characters' stroke (see `type.createOutlines`).
+                    NodeKind::Text(t) => t.runs.iter().any(|r| r.style.has_stroke()),
+                    _ => shape && !c.appearance.stroke_paint().is_none(),
+                };
                 e.gradient |= match &c.kind {
                     NodeKind::Text(t) => t.runs.iter().any(|r| matches!(r.style.fill, Paint::Gradient(_))),
                     _ => shape && c.appearance.items.iter().any(|i| gradient_fill(i).is_some()),
diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs
--- a/crates/engine/src/tests/menucmds.rs
+++ b/crates/engine/src/tests/menucmds.rs
@@ -645,6 +645,31 @@ fn expand_live_shape_and_text_in_one_undo_step() {
     assert!(s.doc().unwrap().doc.node(t).is_some());
 }
 
+#[test]
+fn expand_text_outlines_its_stroke_too() {
+    let mut s = session();
+    let t = text(&mut s, "Hi");
+    sel(&mut s, &[t]);
+    s.execute("paint.setStroke", &json!({"color": "#ff0000"})).unwrap();
+    let info = s.execute("object.expand.info", &json!({})).unwrap();
+    assert_eq!(info["stroke"], json!(true), "stroked type has a stroke to expand");
+    let n = undo_len(&s);
+    s.execute("object.expand", &json!({})).unwrap();
+    assert_eq!(undo_len(&s), n + 1);
+    let d = &s.doc().unwrap().doc;
+    let (mut paths, mut stroked) = (0, 0);
+    for id in &s.doc().unwrap().selection.objects {
+        d.node(*id).unwrap().walk(&mut |c| {
+            if matches!(c.kind, NodeKind::Path { .. } | NodeKind::Compound { .. }) {
+                paths += 1;
+                stroked += usize::from(!c.appearance.stroke_paint().is_none());
+            }
+        });
+    }
+    assert!(paths > 0);
+    assert_eq!(stroked, 0, "every glyph's stroke became a filled outline ({paths} paths)");
+}
+
 #[test]
 fn expand_nothing_errors() {
     let mut s = session();
