diff --git a/misc/bash/completion.sh b/misc/bash/completion.sh
index bc184edd6..bea2a40bc 100644
--- a/misc/bash/completion.sh
+++ b/misc/bash/completion.sh
@@ -4,13 +4,14 @@ function _complete_nix {
     _get_comp_words_by_ref -n ':=&' words cword cur
     local have_type
     while IFS= read -r line; do
+        local completion=${line%%	*}
         if [[ -z $have_type ]]; then
             have_type=1
-            if [[ $line = filenames ]]; then
+            if [[ $completion = filenames ]]; then
                 compopt -o filenames
             fi
         else
-            COMPREPLY+=("$line")
+            COMPREPLY+=("$completion")
         fi
     done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}")
     __ltrim_colon_completions "$cur"
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 3411e2d7a..9151a0344 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -44,7 +44,7 @@ MixCommonArgs::MixCommonArgs(const string & programName)
                 globalConfig.getSettings(settings);
                 for (auto & s : settings)
                     if (hasPrefix(s.first, prefix))
-                        completions->insert(s.first);
+                        completions->add(s.first, s.second.description);
             }
         }
     });
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 2760b830b..66d8df085 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -17,8 +17,19 @@ void Args::addFlag(Flag && flag_)
     if (flag->shortName) shortFlags[flag->shortName] = flag;
 }
 
+void Completions::add(std::string completion, std::string description)
+{
+    insert(Completion{
+            .completion = completion,
+            .description = description
+            });
+}
+
+bool Completion::operator<(const Completion & other) const
+{ return completion < other.completion || (completion == other.completion && description < other.description); }
+
 bool pathCompletions = false;
-std::shared_ptr<std::set<std::string>> completions;
+std::shared_ptr<Completions> completions;
 
 std::string completionMarker = "___COMPLETE___";
 
@@ -148,7 +159,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
             for (auto & [name, flag] : longFlags) {
                 if (!hiddenCategories.count(flag->category)
                     && hasPrefix(name, std::string(*prefix, 2)))
-                    completions->insert("--" + name);
+                    completions->add("--" + name, flag->description);
             }
         }
         auto i = longFlags.find(string(*pos, 2));
@@ -165,9 +176,9 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
 
     if (auto prefix = needsCompletion(*pos)) {
         if (prefix == "-") {
-            completions->insert("--");
-            for (auto & [flag, _] : shortFlags)
-                completions->insert(std::string("-") + flag);
+            completions->add("--");
+            for (auto & [flagName, flag] : shortFlags)
+                completions->add(std::string("-") + flagName, flag->description);
         }
     }
 
@@ -244,11 +255,11 @@ nlohmann::json Args::toJSON()
     return res;
 }
 
-static void hashTypeCompleter(size_t index, std::string_view prefix) 
+static void hashTypeCompleter(size_t index, std::string_view prefix)
 {
     for (auto & type : hashTypes)
         if (hasPrefix(type, prefix))
-            completions->insert(type);
+            completions->add(type);
 }
 
 Args::Flag Args::Flag::mkHashTypeFlag(std::string && longName, HashType * ht)
@@ -292,7 +303,7 @@ static void _completePath(std::string_view prefix, bool onlyDirs)
                 auto st = lstat(globbuf.gl_pathv[i]);
                 if (!S_ISDIR(st.st_mode)) continue;
             }
-            completions->insert(globbuf.gl_pathv[i]);
+            completions->add(globbuf.gl_pathv[i]);
         }
         globfree(&globbuf);
     }
@@ -385,7 +396,7 @@ MultiCommand::MultiCommand(const Commands & commands)
             if (auto prefix = needsCompletion(s)) {
                 for (auto & [name, command] : commands)
                     if (hasPrefix(name, *prefix))
-                        completions->insert(name);
+                        completions->add(name);
             }
             auto i = commands.find(s);
             if (i == commands.end())
diff --git a/src/libutil/args.hh b/src/libutil/args.hh
index f41242e17..26f1bc11b 100644
--- a/src/libutil/args.hh
+++ b/src/libutil/args.hh
@@ -283,7 +283,17 @@ typedef std::vector<std::pair<std::string, std::string>> Table2;
 
 void printTable(std::ostream & out, const Table2 & table);
 
-extern std::shared_ptr<std::set<std::string>> completions;
+struct Completion {
+    std::string completion;
+    std::string description;
+
+    bool operator<(const Completion & other) const;
+};
+class Completions : public std::set<Completion> {
+public:
+    void add(std::string completion, std::string description = "");
+};
+extern std::shared_ptr<Completions> completions;
 extern bool pathCompletions;
 
 std::optional<std::string> needsCompletion(std::string_view s);
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index 9bf6b7caa..7473c9758 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -26,7 +26,7 @@ void completeFlakeInputPath(
     auto flake = flake::getFlake(*evalState, flakeRef, true);
     for (auto & input : flake.inputs)
         if (hasPrefix(input.first, prefix))
-            completions->insert(input.first);
+            completions->add(input.first);
 }
 
 MixFlakeOptions::MixFlakeOptions()
@@ -211,7 +211,7 @@ void completeFlakeRefWithFragment(
                         auto attrPath2 = attr->getAttrPath(attr2);
                         /* Strip the attrpath prefix. */
                         attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
-                        completions->insert(flakeRefS + "#" + concatStringsSep(".", attrPath2));
+                        completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2));
                     }
                 }
             }
@@ -222,7 +222,7 @@ void completeFlakeRefWithFragment(
                 for (auto & attrPath : defaultFlakeAttrPaths) {
                     auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
                     if (!attr) continue;
-                    completions->insert(flakeRefS + "#");
+                    completions->add(flakeRefS + "#");
                 }
             }
         }
@@ -243,7 +243,7 @@ ref<EvalState> EvalCommand::getEvalState()
 void completeFlakeRef(ref<Store> store, std::string_view prefix)
 {
     if (prefix == "")
-        completions->insert(".");
+        completions->add(".");
 
     completeDir(0, prefix);
 
@@ -254,10 +254,10 @@ void completeFlakeRef(ref<Store> store, std::string_view prefix)
             if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
                 std::string from2(from, 6);
                 if (hasPrefix(from2, prefix))
-                    completions->insert(from2);
+                    completions->add(from2);
             } else {
                 if (hasPrefix(from, prefix))
-                    completions->insert(from);
+                    completions->add(from);
             }
         }
     }
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 1e9e07bc0..5056ceb78 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -208,7 +208,7 @@ void mainWrapped(int argc, char * * argv)
         if (completions) {
             std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n");
             for (auto & s : *completions)
-                std::cout << s << "\n";
+                std::cout << s.completion << "\t" << s.description << "\n";
         }
     });