diff --git a/crates/ui-egui/src/grid/menus.rs b/crates/ui-egui/src/grid/menus.rs
--- a/crates/ui-egui/src/grid/menus.rs
+++ b/crates/ui-egui/src/grid/menus.rs
@@ -127,7 +127,6 @@ pub(super) fn context_menu(app: &mut SheetApp, ui: &mut egui::Ui) {
     let mut close = false;
     let resp = egui::Area::new(egui::Id::unique("cell_context")).fixed_pos(at).order(egui::Order::Foreground).show(ui.ctx(), |ui| {
         egui::Frame::popup(ui.style()).show(ui, |ui| {
-            ui.set_min_width(200.0);
             let insert_copied = insert_copied_item(app);
             let mut items: Vec<(&str, &str)> = vec![
                 ("Cut", "edit.cut"),
@@ -161,12 +160,30 @@ pub(super) fn context_menu(app: &mut SheetApp, ui: &mut egui::Ui) {
                 items.insert(i + 2, ("Show Details", "pivot.showDetails"));
             }
             let lang = crate::i18n::current(ui.ctx());
+            // Size the menu from its widest translated command, then keep every button on that
+            // same width. Otherwise a long item can wrap or expand only while hovered.
+            let menu_width = items
+                .iter()
+                .filter(|(label, _)| *label != "-")
+                .map(|(label, _)| {
+                    ui.painter()
+                        .layout_no_wrap(
+                            lang.tr(label).to_owned(),
+                            ui.style().text_styles[&egui::TextStyle::Button].clone(),
+                            ui.visuals().text_color(),
+                        )
+                        .size()
+                        .x
+                })
+                .fold(190.0_f32, f32::max)
+                + 24.0;
+            ui.set_min_width(menu_width);
             for (label, id) in items {
                 if label == "-" {
                     ui.separator();
                     continue;
                 }
-                if ui.add(egui::Button::new(lang.tr(label)).frame_when_inactive(false).min_size(vec2(190.0, 20.0))).clicked() {
+                if ui.add(egui::Button::new(lang.tr(label)).frame_when_inactive(false).min_size(vec2(menu_width, 20.0))).clicked() {
                     close = true;
                     match id {
                         "ui:pasteSpecial" => app.open_dialog("pasteSpecial", json!({})),
diff --git a/crates/ui-egui/tests/it/context_menu_stays_open.rs b/crates/ui-egui/tests/it/context_menu_stays_open.rs
--- a/crates/ui-egui/tests/it/context_menu_stays_open.rs
+++ b/crates/ui-egui/tests/it/context_menu_stays_open.rs
@@ -2,6 +2,7 @@
 //! an item, presses Escape or clicks elsewhere.
 
 use egui::{Event, Modifiers, PointerButton, Pos2};
+use egui_kittest::kittest::Queryable as _;
 use gridcraft_engine::Session;
 use gridcraft_engine::core::CellRef;
 use gridcraft_ui_egui::{SheetApp, grid};
@@ -100,3 +101,20 @@ fn a_quick_right_click_between_pixels_keeps_the_menu_open_on_retina() {
     h.run_steps(4);
     assert!(h.state().grid.context_menu.is_some(), "the menu stays open");
 }
+
+#[test]
+/// Hovering a long command keeps both its size and rendered position fixed.
+fn hovering_a_long_context_item_does_not_resize_the_menu() {
+    let mut h = harness();
+    quick_right_click(&mut h, "C5");
+    let item = h.get_by_label("Filter by Selected Cell's Value").rect();
+    let menu = h.state().grid.context_menu.unwrap();
+    h.input_mut().events.push(Event::PointerMoved(item.center()));
+    h.run_steps(2);
+
+    let hovered_item = h.get_by_label("Filter by Selected Cell's Value").rect();
+    assert_eq!(hovered_item.size(), item.size(), "hovering keeps the command's dimensions fixed");
+    assert_eq!(hovered_item.min, item.min, "hovering keeps the rendered command position fixed");
+    let menu_after = h.state().grid.context_menu.unwrap();
+    assert_eq!(menu_after, menu, "hovering does not move the menu anchor");
+}
