diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs
index 77169a432fc..db5ee14bc29 100644
--- a/src/cargo/util/command_prelude.rs
+++ b/src/cargo/util/command_prelude.rs
@@ -246,7 +246,11 @@ pub trait CommandExt: Sized {
                 "Space or comma separated list of features to activate",
             )
             .short('F')
-            .help_heading(heading::FEATURE_SELECTION),
+            .help_heading(heading::FEATURE_SELECTION)
+            .add(clap_complete::ArgValueCandidates::new(|| {
+                let candidates = get_feature_candidates();
+                candidates.unwrap_or_default()
+            })),
         )
         ._arg(
             flag("all-features", "Activate all available features")
@@ -1169,6 +1173,28 @@ fn default_profile_candidates() -> Vec<clap_complete::CompletionCandidate> {
     ]
 }
 
+fn get_feature_candidates() -> CargoResult<Vec<clap_complete::CompletionCandidate>> {
+    let gctx = new_gctx_for_completions()?;
+    let manifest_path = find_root_manifest_for_wd(gctx.cwd())?;
+    let ws = Workspace::new(&manifest_path, &gctx)?;
+    let mut feature_candidates = Vec::new();
+
+    // Process all packages in the workspace
+    for package in ws.members() {
+        let package_name = package.name();
+
+        // Add direct features with package info
+        for feature_name in package.summary().features().keys() {
+            feature_candidates.push(
+                clap_complete::CompletionCandidate::new(feature_name)
+                    .help(Some(format!("(from {})", package_name).into())),
+            );
+        }
+    }
+
+    Ok(feature_candidates)
+}
+
 fn get_example_candidates() -> Vec<clap_complete::CompletionCandidate> {
     get_targets_from_metadata()
         .unwrap_or_default()