diff --git a/modules/gui/qt/Makefile.am b/modules/gui/qt/Makefile.am
index 296e951fc4d34850135ed7cf7c0ed3431566e603..6254d625372b0c2f4bc8565d8b64dd8bb38af230 100644
--- a/modules/gui/qt/Makefile.am
+++ b/modules/gui/qt/Makefile.am
@@ -883,6 +883,7 @@ libqt_plugin_la_QML = \
 	gui/qt/util/qml/BindingRev8.qml \
 	gui/qt/util/qml/BindingRev14.qml \
 	gui/qt/util/qml/VanillaObject.qml \
+	gui/qt/util/qml/NativeMenu.qml \
 	gui/qt/widgets/qml/ActionButtonOverlay.qml \
 	gui/qt/widgets/qml/ActionButtonPrimary.qml \
 	gui/qt/widgets/qml/BannerTabButton.qml \
diff --git a/modules/gui/qt/util/qml/NativeMenu.qml b/modules/gui/qt/util/qml/NativeMenu.qml
new file mode 100644
index 0000000000000000000000000000000000000000..cfc5b2e15182baa1306fd1c9a51b4fdb9a436463
--- /dev/null
+++ b/modules/gui/qt/util/qml/NativeMenu.qml
@@ -0,0 +1,115 @@
+
+/*****************************************************************************
+ * Copyright (C) 2022 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * ( at your option ) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+import QtQml 2.11
+
+import org.videolan.vlc 0.1
+
+
+// @brief - a class that can be used to create native menus with support
+// to asyncronously retreive data from MLBaseModel like model
+
+VanillaObject {
+    id: root
+
+    /**
+      actions - list of action to show
+
+      each action is an Object and contains following keys
+        "text" - (string) display text of the action
+
+        "action" - (function(dataList, options, indexes)) a function that will be called when action is selected
+
+        "visible" - (Boolean or function(options, indexes) -> Boolean) (optional) a boolean value or function which
+                    controls whether the action is shown in the menu following 'popup' call
+
+       e.g see MLContextMenu.qml
+    **/
+
+    property var actions: []
+
+    signal requestData(var requestID, var indexes)
+
+
+    property var _options: null
+
+    property var _indexes: []
+
+    property bool _pendingData: false
+
+    property var _dataList: null
+
+    property int _currentRequest: 0
+
+    property int _actionOnDataReceived: -1
+
+    property var _effectiveActions: null
+
+
+    function popup(_indexes, point, _options) {
+        root._options = _options
+        root._indexes = _indexes
+        _actionOnDataReceived = -1
+        _pendingData = true
+        _dataList = null
+
+        var requestID = ++_currentRequest
+        requestData(requestID, _indexes)
+
+        var textStrings = []
+        _effectiveActions = []
+        for (var i in actions) {
+            if (!actions[i].hasOwnProperty("visible")
+                    || (typeof actions[i].visible === "boolean" && actions[i].visible)
+                    || (typeof actions[i].visible === "function" && actions[i].visible(_options, _indexes))) {
+                _effectiveActions.push(actions[i])
+                textStrings.push(actions[i].text)
+            }
+        }
+
+        menu.popup(point, textStrings)
+    }
+
+    function setData(id, data) {
+        if (_currentRequest !== id)
+            return;
+
+        _dataList = data
+        _pendingData = false
+
+        if (_actionOnDataReceived !== -1)
+           _executeAction(_actionOnDataReceived)
+    }
+
+    function _executeAction(index) {
+        var action = root._effectiveActions[index]
+        action.action(_dataList, _options, _indexes)
+    }
+
+
+    StringListMenu {
+        id: menu
+
+        onSelected: {
+            if (root._pendingData)
+                root._actionOnDataReceived = index
+            else
+                root._executeAction(index)
+        }
+    }
+}
diff --git a/modules/gui/qt/vlc.qrc b/modules/gui/qt/vlc.qrc
index 5fc6a267aba2121112b115c2a0ea0b774158f46c..184c23fa03ddd0f3ee970b2875d9f9fc79a69d82 100644
--- a/modules/gui/qt/vlc.qrc
+++ b/modules/gui/qt/vlc.qrc
@@ -33,6 +33,7 @@
         <file alias="BindingRev8.qml">util/qml/BindingRev8.qml</file>
         <file alias="BindingRev14.qml">util/qml/BindingRev14.qml</file>
         <file alias="VanillaObject.qml">util/qml/VanillaObject.qml</file>
+        <file alias="NativeMenu.qml">util/qml/NativeMenu.qml</file>
     </qresource>
     <qresource prefix="/toolbar">
         <file alias="faster.svg">pixmaps/faster.svg</file>