diff --git a/crates/ui-egui/src/menus/state.rs b/crates/ui-egui/src/menus/state.rs
--- a/crates/ui-egui/src/menus/state.rs
+++ b/crates/ui-egui/src/menus/state.rs
@@ -38,7 +38,7 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option<bool> {
     };
     Some(match id {
         "type.orientation.vertical" | "type.orientation.horizontal" => {
-            selected_text(false).is_some_and(|t| t.vertical == (id == "type.orientation.vertical"))
+            crate::panels::character::shared_text_flag(app, |t| t.vertical) == Some(id == "type.orientation.vertical")
         }
         "type.opticalMarginAlignment" => crate::panels::character::shared_text_flag(app, |t| t.optical_margin) == Some(true),
         "view.outline" => v.outline,
diff --git a/crates/ui-egui/src/menus/tests.rs b/crates/ui-egui/src/menus/tests.rs
--- a/crates/ui-egui/src/menus/tests.rs
+++ b/crates/ui-egui/src/menus/tests.rs
@@ -255,6 +255,43 @@ fn optical_margin_alignment_menu_unchecked_when_shared_flag_none() {
     assert_eq!(checked(&app, id, &Value::Null), Some(true));
 }
 
+/// Type › Orientation menu checks use `shared_text_flag`; mixed values leave both unchecked.
+#[test]
+fn type_orientation_menu_unchecked_when_shared_vertical_none() {
+    let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
+    app.run("file.new", json!({})).unwrap();
+    let h = app.run("text.create", json!({"x": 10, "y": 40, "text": "H"})).unwrap()["id"].clone();
+    let v = app.run("text.create", json!({"x": 10, "y": 80, "text": "V", "vertical": true})).unwrap()["id"].clone();
+    app.run("select.set", json!({"ids": [h, v]})).unwrap();
+    assert_eq!(crate::panels::character::shared_text_flag(&app, |t| t.vertical), None);
+    assert_eq!(checked(&app, "type.orientation.vertical", &Value::Null), Some(false));
+    assert_eq!(checked(&app, "type.orientation.horizontal", &Value::Null), Some(false));
+    app.run("select.set", json!({"ids": [v]})).unwrap();
+    assert_eq!(checked(&app, "type.orientation.vertical", &Value::Null), Some(true));
+    assert_eq!(checked(&app, "type.orientation.horizontal", &Value::Null), Some(false));
+}
+
+#[test]
+fn type_orientation_menu_unchecked_for_three_object_mixed_selections() {
+    let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
+    app.run("file.new", json!({})).unwrap();
+    let v0 = app.run("text.create", json!({"x": 10, "y": 40, "text": "v0", "vertical": true})).unwrap()["id"].clone();
+    let h1 = app.run("text.create", json!({"x": 10, "y": 80, "text": "h1"})).unwrap()["id"].clone();
+    let h2 = app.run("text.create", json!({"x": 10, "y": 120, "text": "h2"})).unwrap()["id"].clone();
+    app.run("select.set", json!({"ids": [v0.clone(), h1.clone(), h2.clone()]})).unwrap();
+    assert_eq!(checked(&app, "type.orientation.vertical", &Value::Null), Some(false));
+    assert_eq!(checked(&app, "type.orientation.horizontal", &Value::Null), Some(false));
+    let h0 = app.run("text.create", json!({"x": 10, "y": 160, "text": "h0"})).unwrap()["id"].clone();
+    let v1 = app.run("text.create", json!({"x": 10, "y": 200, "text": "v1", "vertical": true})).unwrap()["id"].clone();
+    let v2 = app.run("text.create", json!({"x": 10, "y": 240, "text": "v2", "vertical": true})).unwrap()["id"].clone();
+    app.run("select.set", json!({"ids": [h0.clone(), v1.clone(), v2.clone()]})).unwrap();
+    assert_eq!(checked(&app, "type.orientation.vertical", &Value::Null), Some(false));
+    assert_eq!(checked(&app, "type.orientation.horizontal", &Value::Null), Some(false));
+    app.run("select.set", json!({"ids": [v0.clone(), v1.clone(), v2.clone()]})).unwrap();
+    assert_eq!(checked(&app, "type.orientation.vertical", &Value::Null), Some(true));
+    assert_eq!(checked(&app, "type.orientation.horizontal", &Value::Null), Some(false));
+}
+
 #[test]
 fn panel_ids_are_case_insensitive_and_labels_work() {
     assert_eq!(normalize_panel("layers"), Some("layers"));
diff --git a/crates/ui-egui/src/panels/character/tests.rs b/crates/ui-egui/src/panels/character/tests.rs
--- a/crates/ui-egui/src/panels/character/tests.rs
+++ b/crates/ui-egui/src/panels/character/tests.rs
@@ -512,6 +512,24 @@ fn shared_text_flag_optical_margin_three_object_selections() {
     assert_eq!(shared_text_flag(&app, |t| t.optical_margin), Some(true));
 }
 
+#[test]
+fn shared_text_flag_vertical_three_object_selections() {
+    let mut app = VectorcraftApp::new(Session::new(), Default::default());
+    app.run("file.new", json!({})).unwrap();
+    let v0 = app.run("text.create", json!({"x": 10, "y": 40, "text": "v0", "vertical": true})).unwrap()["id"].clone();
+    let h1 = app.run("text.create", json!({"x": 10, "y": 80, "text": "h1"})).unwrap()["id"].clone();
+    let h2 = app.run("text.create", json!({"x": 10, "y": 120, "text": "h2"})).unwrap()["id"].clone();
+    app.run("select.set", json!({"ids": [v0.clone(), h1.clone(), h2.clone()]})).unwrap();
+    assert_eq!(shared_text_flag(&app, |t| t.vertical), None);
+    let h0 = app.run("text.create", json!({"x": 10, "y": 160, "text": "h0"})).unwrap()["id"].clone();
+    let v1 = app.run("text.create", json!({"x": 10, "y": 200, "text": "v1", "vertical": true})).unwrap()["id"].clone();
+    let v2 = app.run("text.create", json!({"x": 10, "y": 240, "text": "v2", "vertical": true})).unwrap()["id"].clone();
+    app.run("select.set", json!({"ids": [h0.clone(), v1.clone(), v2.clone()]})).unwrap();
+    assert_eq!(shared_text_flag(&app, |t| t.vertical), None);
+    app.run("select.set", json!({"ids": [v0.clone(), v1.clone(), v2.clone()]})).unwrap();
+    assert_eq!(shared_text_flag(&app, |t| t.vertical), Some(true));
+}
+
 /// Small Caps, Superscript and Subscript buttons and menu items stay off when the selection mixes them.
 #[test]
 fn character_mixed_small_caps_and_position_show_off() {
