diff --git a/crates/ui-egui/src/add_animation_menu_tests.rs b/crates/ui-egui/src/add_animation_menu_tests.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/add_animation_menu_tests.rs
@@ -0,0 +1,61 @@
+//! Add Animation (#1370): the menu scrolls, so the last effect can be reached and added.
+
+use deckcraft_engine::Session;
+use egui_kittest::Harness;
+use egui_kittest::kittest::Queryable;
+use serde_json::json;
+
+use crate::{Services, SlideApp};
+
+fn harness(size: egui::Vec2) -> Harness<'static, SlideApp> {
+    let mut session = Session::with_new();
+    let id = session.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 200.0, 120.0]})).unwrap()["id"].as_u64().unwrap();
+    session.execute("edit.select", &json!({"ids": [id]})).unwrap();
+    let mut app = SlideApp::new(session, Services::default());
+    app.ui.tab = "Animations".into();
+    let mut h = Harness::builder().with_size(size).with_step_dt(1.0 / 60.0).build_ui_state(
+        |ui, app: &mut SlideApp| {
+            app.logic(ui.ctx());
+            app.ui(ui);
+        },
+        app,
+    );
+    h.run_steps(3);
+    h
+}
+
+fn open_add_animation(h: &mut Harness<'static, SlideApp>, window: egui::Vec2) {
+    // The Animations ribbon is wider than a short window. The edge chevron is what a person uses
+    // to reach Add Animation; scroll-into-view on the button does not move that bar.
+    for _ in 0..12 {
+        let rect = h.get_by_label("Add Animation").rect();
+        if rect.min.x >= 8.0 && rect.max.x <= window.x - 8.0 {
+            break;
+        }
+        let Some(cue) = h.query_by_label("Scroll ribbon right") else { break };
+        cue.click();
+        h.run_steps(2);
+    }
+    h.get_by_label("Add Animation").click();
+    h.run_steps(3);
+}
+
+fn added_effect(h: &Harness<'_, SlideApp>) -> Option<(String, deckcraft_model::anim::AnimClass)> {
+    h.state().session.active().and_then(|d| d.current_slide()).and_then(|s| s.animations.first()).map(|a| (a.effect.clone(), a.class))
+}
+
+#[test]
+fn the_last_effect_is_reachable_by_scrolling() {
+    let size = egui::vec2(800.0, 700.0);
+    let mut h = harness(size);
+    open_add_animation(&mut h, size);
+    let before = h.get_by_label("Neutron — Motion").rect();
+    assert!(before.min.y > 700.0, "the last effect starts below the window: {before:?}");
+    h.get_by_label("Neutron — Motion").scroll_to_me();
+    h.run_steps(4);
+    let row = h.get_by_label("Neutron — Motion").rect();
+    assert!(row.min.y >= 0.0 && row.max.y <= 700.0, "a scrolled effect stays inside the window: {row:?}");
+    h.get_by_label("Neutron — Motion").click();
+    h.run_steps(3);
+    assert_eq!(added_effect(&h), Some(("neutron".into(), deckcraft_model::anim::AnimClass::Path)));
+}
diff --git a/crates/ui-egui/src/ribbon.rs b/crates/ui-egui/src/ribbon.rs
--- a/crates/ui-egui/src/ribbon.rs
+++ b/crates/ui-egui/src/ribbon.rs
@@ -2091,24 +2091,29 @@ fn animations(app: &mut SlideApp, ui: &mut Ui) {
     });
     group(ui, |ui| {
         let r = big_button(ui, Icon::AddAnimation, "Add\nAnimation", has_sel);
+        // Every effect in one column is taller than a 1440p screen, so cap the height and scroll (#1370).
+        // The menu opens under the title bar and the ribbon; keep that chrome out of the cap.
+        let max_h = (r.ctx.viewport_rect().height() - 160.0).max(120.0);
         egui::Popup::menu(&r).width(260.0).show(|ui| {
-            for a in deckcraft_model::anim::ANIMATIONS.iter().filter(|a| a.2 != AnimClass::Media) {
-                if ui
-                    .button(format!(
-                        "{} — {}",
-                        a.1,
-                        match a.2 {
-                            AnimClass::Entrance => "Entrance",
-                            AnimClass::Emphasis => "Emphasis",
-                            AnimClass::Exit => "Exit",
-                            _ => "Motion",
-                        }
-                    ))
-                    .clicked()
-                {
-                    run(app, "animation.add", json!({"effect": a.0, "class": a.2.xml()}));
+            egui::ScrollArea::vertical().id_salt("add_animation").max_height(max_h).show(ui, |ui| {
+                for a in deckcraft_model::anim::ANIMATIONS.iter().filter(|a| a.2 != AnimClass::Media) {
+                    if ui
+                        .button(format!(
+                            "{} — {}",
+                            a.1,
+                            match a.2 {
+                                AnimClass::Entrance => "Entrance",
+                                AnimClass::Emphasis => "Emphasis",
+                                AnimClass::Exit => "Exit",
+                                _ => "Motion",
+                            }
+                        ))
+                        .clicked()
+                    {
+                        run(app, "animation.add", json!({"effect": a.0, "class": a.2.xml()}));
+                    }
                 }
-            }
+            });
         });
         big(app, ui, Icon::AnimationPane, "Animation\nPane", "view.pane", json!({"pane": "animation"}));
         let r = big_button(ui, Icon::Trigger, "Trigger", cur.is_some());
@@ -3633,6 +3638,10 @@ mod master_layout_tests;
 #[path = "icons_popup_tests.rs"]
 mod icons_popup_tests;
 
+#[cfg(test)]
+#[path = "add_animation_menu_tests.rs"]
+mod add_animation_menu_tests;
+
 #[cfg(test)]
 #[path = "align_tests.rs"]
 mod align_tests;
