diff --git a/contributors/people.toml b/contributors/people.toml
--- a/contributors/people.toml
+++ b/contributors/people.toml
@@ -282,3 +282,6 @@ sync_github_name = false
 [people.jasallen]
 real_name = "Jason DeBoever"
 sync_github_name = true
+
+[people.Luis-0508]
+sync_github_name = true
diff --git a/crates/ui-egui/src/dialogs/filter_dialog.rs b/crates/ui-egui/src/dialogs/filter_dialog.rs
--- a/crates/ui-egui/src/dialogs/filter_dialog.rs
+++ b/crates/ui-egui/src/dialogs/filter_dialog.rs
@@ -421,6 +421,42 @@ fn apply_command_defaults(app: &PhotocraftApp, command: &str, fields: &mut Map<S
     if command == REPLACE_COLOR {
         fields.insert(COLOR_EYEDROPPER.into(), json!(true));
     }
+    if command == COLOR_LOOKUP {
+        fields.insert(LUT_FOLDER.into(), json!(app.session.prefs().file_handling.lut_folder));
+    }
+}
+
+/// Image › Adjustments › Color Lookup: its 3D LUT menu lists the LUT folder's files after the
+/// built-in looks (#9754), as the adjustment layer's Properties do. `__lutFolder` is the folder
+/// (Preferences › File Handling › LUT Folder); a file picked from it goes to `file`, and
+/// `__folderLut` remembers that pick, so it stays a menu entry even once the file has left the
+/// folder.
+const COLOR_LOOKUP: &str = "image.adjustments.colorLookup";
+const LUT_FOLDER: &str = "__lutFolder";
+const FOLDER_LUT_PICK: &str = "__folderLut";
+
+/// The `lut` value to keep after the Color Lookup menu chose `cur`: a LUT folder file is set as
+/// `file` with `lut` at `none`. Leaving a folder file the menu showed (`was_folder`) for another
+/// entry clears `file`, so the built-in look or None applies instead of the old file.
+fn pick_folder_lut(f: &mut Map<String, Value>, cur: String, was_folder: bool) -> String {
+    if let Some(path) = cur.strip_prefix(crate::adjust_ui::FOLDER_LUT) {
+        f.insert("file".into(), json!(path));
+        f.insert(FOLDER_LUT_PICK.into(), json!(path));
+        return "none".into();
+    }
+    if was_folder {
+        f.insert("file".into(), json!(""));
+        f.remove(FOLDER_LUT_PICK);
+    }
+    cur
+}
+
+/// Whether the Color Lookup menu shows `file` as a LUT folder entry: it is in the folder's scan
+/// (`luts`), or it was picked from the menu and has left the folder since.
+fn shows_folder_lut(f: &Map<String, Value>, cur: &str, luts: &[(String, String)]) -> bool {
+    let file = f.get("file").and_then(Value::as_str).unwrap_or_default();
+    let picked = f.get(FOLDER_LUT_PICK).and_then(Value::as_str) == Some(file);
+    cur == "none" && !file.is_empty() && (picked || luts.iter().any(|(_, path)| path == file))
 }
 
 /// Assign Profile offers only the profiles the document's colour mode can take (#4951): the
@@ -892,7 +928,26 @@ pub fn body(ui: &mut egui::Ui, f: &mut Map<String, Value>) {
                     if lists_installed && !opts.iter().any(|(v, _)| *v == cur) {
                         opts.push((cur.clone(), stem.as_str()));
                     }
+                    let folder_lut = cmd == COLOR_LOOKUP && p.key == "lut";
+                    let lut_folder = f.get(LUT_FOLDER).and_then(Value::as_str).filter(|d| folder_lut && !d.is_empty());
+                    let luts = lut_folder.map(|d| crate::adjust_ui::folder_luts(d, ui.ctx())).unwrap_or_default();
+                    let lut_keys: Vec<String> = luts.iter().map(|(_, path)| format!("{}{path}", crate::adjust_ui::FOLDER_LUT)).collect();
+                    opts.extend(lut_keys.iter().cloned().zip(luts.iter().map(|(name, _)| name.as_str())));
+                    // A folder file is kept as `lut: none` + `file`: show it by its name, also once
+                    // it has left the folder, so picking another entry still clears `file`.
+                    let file = f.get("file").and_then(Value::as_str).unwrap_or_default();
+                    let was_folder = shows_folder_lut(f, &cur, &luts);
+                    let gone_name = std::path::Path::new(file).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
+                    if was_folder {
+                        cur = format!("{}{file}", crate::adjust_ui::FOLDER_LUT);
+                        if !opts.iter().any(|(v, _)| *v == cur) {
+                            opts.push((cur.clone(), gone_name.as_str()));
+                        }
+                    }
                     crate::widgets::dropdown(ui, &format!("flt-{cmd}-{}", p.key), &label(&p.key), &mut cur, &opts, 170.0);
+                    if folder_lut {
+                        cur = pick_folder_lut(f, cur, was_folder);
+                    }
                     if let Some(i) = cur.strip_prefix("channel:").filter(|_| depth).and_then(|i| i.parse::<usize>().ok()) {
                         f.insert("depthChannel".into(), json!(i));
                         cur = "channel".into();
diff --git a/crates/ui-egui/src/dialogs/filter_dialog/tests.rs b/crates/ui-egui/src/dialogs/filter_dialog/tests.rs
--- a/crates/ui-egui/src/dialogs/filter_dialog/tests.rs
+++ b/crates/ui-egui/src/dialogs/filter_dialog/tests.rs
@@ -467,6 +467,58 @@ fn indexed_counts_are_integer_json_while_dither_amounts_keep_their_decimals() {
 /// #7400: Lens Blur's Source lists the document's alpha channels, by name, each choosable even
 /// when two share a name; `depthChannel` is set through that menu, not shown as a field, and
 /// like the other references it isn't remembered, so a remembered `channel` source opens as None.
+/// #9754: Image › Adjustments › Color Lookup lists the LUT folder's files, and one picked from
+/// it is applied through `file`.
+#[test]
+fn color_lookup_dialog_lists_and_applies_lut_folder_files() {
+    let tmp = tempfile::tempdir().unwrap();
+    let path = tmp.path().join("film.cube");
+    std::fs::write(&path, "LUT_3D_SIZE 2\n0 0 0\n1 0 0\n0 1 0\n1 1 0\n0 0 1\n1 0 1\n0 1 1\n1 1 1\n").unwrap();
+    let path = path.to_str().unwrap().to_string();
+    let key = format!("{}{path}", crate::adjust_ui::FOLDER_LUT);
+    let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
+    app.run("prefs.set", json!({"path": "fileHandling.lutFolder", "value": tmp.path().to_str().unwrap()})).unwrap();
+    app.run("file.new", json!({"width": 16, "height": 12})).unwrap();
+    let id = open(&mut app, COLOR_LOOKUP).unwrap();
+    let mut f = app.ui.dialog_mut(id).unwrap().fields.clone();
+    assert_eq!(f[LUT_FOLDER], json!(tmp.path().to_str().unwrap()));
+    // The menu's folder scan runs on a worker thread: draw until it has finished.
+    let ctx = egui::Context::default();
+    for _ in 0..500 {
+        if !crate::adjust_ui::folder_luts(tmp.path().to_str().unwrap(), &ctx).is_empty() {
+            break;
+        }
+        std::thread::sleep(std::time::Duration::from_millis(10));
+    }
+    assert_eq!(crate::adjust_ui::folder_luts(tmp.path().to_str().unwrap(), &ctx), [("film.cube".to_string(), path.clone())]);
+
+    // Picking the folder file: `file` is set and `lut` stays a valid built-in value.
+    let lut = pick_folder_lut(&mut f, key.clone(), false);
+    f.insert("lut".into(), json!(lut));
+    assert_eq!((&f["lut"], &f["file"]), (&json!("none"), &json!(path)));
+    // Drawn again, the menu keeps showing it (and so keeps it).
+    ctx.run_ui(Default::default(), |ui| body(ui, &mut f)).textures_delta.clear();
+    assert_eq!((&f["lut"], &f["file"]), (&json!("none"), &json!(path)));
+    app.run(COLOR_LOOKUP, params_of(&f)).unwrap();
+
+    // Leaving it for a built-in look drops the file, so the look applies.
+    assert_eq!(pick_folder_lut(&mut f, "warm".into(), true), "warm");
+    assert_eq!(f["file"], json!(""));
+    // A picked file that has left the folder since (no longer in the scan) still shows as its
+    // entry, so leaving it for a built-in look still drops the file.
+    assert_eq!(pick_folder_lut(&mut f, key.clone(), false), "none");
+    let was_folder = shows_folder_lut(&f, "none", &[]);
+    assert!(was_folder);
+    assert_eq!(pick_folder_lut(&mut f, "warm".into(), was_folder), "warm");
+    assert_eq!(f["file"], json!(""));
+    assert!(!f.contains_key(FOLDER_LUT_PICK));
+    // A path typed into File isn't touched by a built-in pick.
+    f.insert("file".into(), json!(path));
+    assert!(!shows_folder_lut(&f, "none", &[]));
+    assert_eq!(pick_folder_lut(&mut f, "cool".into(), false), "cool");
+    assert_eq!(f["file"], json!(path));
+}
+
 #[test]
 fn lens_blur_source_lists_the_alpha_channels() {
     let spec = photocraft_engine::commands::find(LENS_BLUR).unwrap().params;
@@ -1546,3 +1598,52 @@ fn replace_color_localized_color_clusters() {
     assert!(px(2)[1] > 0.5, "near the point: shifted to green {:?}", px(2));
     assert!(px(38)[0] > 0.9 && px(38)[1] < 0.1, "far away: still red {:?}", px(38));
 }
+
+/// #9754: exercise the real dropdown, not the selection helper, with every listed extension.
+/// Only the CUBE fixture is parsed here; the other files establish listing eligibility.
+#[test]
+fn color_lookup_folder_menu_click_applies_pixels_and_undoes() {
+    use egui_kittest::Harness;
+    use egui_kittest::kittest::Queryable;
+
+    let tmp = tempfile::tempdir().unwrap();
+    let path = tmp.path().join("invert.cube");
+    std::fs::write(&path, "LUT_3D_SIZE 2\n1 1 1\n0 1 1\n1 0 1\n0 0 1\n1 1 0\n0 1 0\n1 0 0\n0 0 0\n").unwrap();
+    for name in ["listed.3dl", "listed.look"] {
+        std::fs::write(tmp.path().join(name), "listing-only fixture").unwrap();
+    }
+    let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
+    app.run("prefs.set", json!({"path": "fileHandling.lutFolder", "value": tmp.path().to_str().unwrap()})).unwrap();
+    app.run("file.new", json!({"width": 4, "height": 4, "background": "#ff0000"})).unwrap();
+    let mut h = Harness::builder().with_size(egui::vec2(900.0, 800.0)).build_ui_state(|ui, app| crate::dialogs::show(app, ui.ctx()), &mut app);
+    PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::ALL[0]);
+    let id = open(h.state_mut(), "image.adjustments.colorLookup").unwrap();
+    h.run_steps(3);
+    for _ in 0..500 {
+        if crate::adjust_ui::folder_luts(tmp.path().to_str().unwrap(), &h.ctx).len() == 3 {
+            break;
+        }
+        std::thread::sleep(std::time::Duration::from_millis(10));
+        h.run_steps(1);
+    }
+    h.get_by_role_and_label(egui::accesskit::Role::ComboBox, "Lut").click();
+    h.run_steps(2);
+    let _ = h.get_by_label("listed.3dl");
+    let _ = h.get_by_label("listed.look");
+    h.get_by_label("invert.cube").click();
+    h.run_steps(3);
+    let f = h.state_mut().ui.dialog_mut(id).unwrap().fields.clone();
+    assert_eq!(f["file"], json!(path.to_str().unwrap()));
+    assert_eq!(f["lut"], json!("none"));
+    let submitted = params_of(&f);
+    assert!(!submitted.as_object().unwrap().keys().any(|key| key.starts_with("__")));
+    crate::dialogs::confirm(h.state_mut(), id).unwrap();
+    let pixel = |app: &PhotocraftApp| app.session.active().unwrap().doc.layers[0].surface().unwrap().read_region(photocraft_geom::Rect::new(0, 0, 1, 1));
+    let cyan = pixel(h.state());
+    assert!(cyan[0] < 0.01 && cyan[1] > 0.99 && cyan[2] > 0.99, "selected nonidentity LUT applied: {cyan:?}");
+    h.state_mut().run("edit.undo", json!({})).unwrap();
+    let red = pixel(h.state());
+    assert!(red[0] > 0.99 && red[1] < 0.01 && red[2] < 0.01, "Undo restores pixels: {red:?}");
+    h.state_mut().run("edit.redo", json!({})).unwrap();
+    assert_eq!(pixel(h.state()), cyan);
+}
diff --git a/crates/ui-egui/src/panels/adjust_ui.rs b/crates/ui-egui/src/panels/adjust_ui.rs
--- a/crates/ui-egui/src/panels/adjust_ui.rs
+++ b/crates/ui-egui/src/panels/adjust_ui.rs
@@ -93,7 +93,7 @@ fn browse_color_lookup(app: &mut PhotocraftApp, layer: LayerId) -> Result<Value,
 }
 
 /// Dropdown key prefix of a LUT listed from the LUT folder; the rest of the key is its path.
-const FOLDER_LUT: &str = "file:";
+pub(crate) const FOLDER_LUT: &str = "file:";
 
 type FolderLuts = Vec<(String, String)>;
 
@@ -109,9 +109,10 @@ struct FolderScan {
 
 /// The LUT folder's files (Preferences › File Handling › LUT Folder, #6558). The folder is
 /// scanned on a worker thread at most every couple of seconds, since a network share can be
-/// slow; until a scan finishes the dropdown shows the previous one.
-fn folder_luts(app: &PhotocraftApp, ctx: &egui::Context) -> FolderLuts {
-    let folder = app.session.prefs().file_handling.lut_folder.clone();
+/// slow; until a scan finishes the dropdown shows the previous one. Image › Adjustments › Color
+/// Lookup lists them too (#9754).
+pub(crate) fn folder_luts(folder: &str, ctx: &egui::Context) -> FolderLuts {
+    let folder = folder.to_string();
     let now = ctx.input(|input| input.time);
     let key = egui::Id::unique("color-lookup-folder");
     let mut scan = ctx.data(|data| data.get_temp::<FolderScan>(key)).unwrap_or_default();
@@ -178,7 +179,7 @@ pub fn color_lookup_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, id: Layer
         return;
     };
     let builtins = photocraft_engine::adjust_cmds::LOOKS;
-    let folder = folder_luts(app, ui.ctx());
+    let folder = folder_luts(&app.session.prefs().file_handling.lut_folder, ui.ctx());
     let current = current_lookup(name, lut.is_some(), &folder);
     let current = app
         .live_adjust
@@ -315,13 +316,14 @@ mod lookup_picker_tests {
         app.run("prefs.set", json!({"path": "fileHandling.lutFolder", "value": dir.to_str().unwrap()})).unwrap();
         // The scan runs on a worker thread: ask again until it has finished.
         let ctx = egui::Context::default();
-        let mut folder = folder_luts(&app, &ctx);
+        let lut_folder = app.session.prefs().file_handling.lut_folder.clone();
+        let mut folder = folder_luts(&lut_folder, &ctx);
         for _ in 0..500 {
             if !folder.is_empty() {
                 break;
             }
             std::thread::sleep(std::time::Duration::from_millis(10));
-            folder = folder_luts(&app, &ctx);
+            folder = folder_luts(&lut_folder, &ctx);
         }
         let key = format!("{FOLDER_LUT}{}", path.to_str().unwrap());
         assert_eq!(folder, [("film.cube".to_string(), path.to_str().unwrap().to_string())]);
