diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index a44815e71e..732e8c276c 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -152,6 +152,8 @@ add_executable(yuzu
     main.ui
     uisettings.cpp
     uisettings.h
+    util/controller_navigation.cpp
+    util/controller_navigation.h
     util/limitable_input_dialog.cpp
     util/limitable_input_dialog.h
     util/overlay_dialog.cpp
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index cc343e5aea..7ab9ced3de 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -7,7 +7,6 @@
 #include <array>
 #include <memory>
 #include <QDialog>
-#include "core/core.h"
 #include "core/frontend/applets/controller.h"
 
 class GMainWindow;
@@ -32,8 +31,9 @@ class System;
 }
 
 namespace Core::HID {
+class HIDCore;
 enum class NpadStyleIndex : u8;
-}
+} // namespace Core::HID
 
 class QtControllerSelectorDialog final : public QDialog {
     Q_OBJECT
diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp
index a56638e211..7b19f1f8de 100644
--- a/src/yuzu/applets/qt_profile_select.cpp
+++ b/src/yuzu/applets/qt_profile_select.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <mutex>
+#include <QApplication>
 #include <QDialogButtonBox>
 #include <QHeaderView>
 #include <QLabel>
@@ -16,6 +17,7 @@
 #include "core/hle/lock.h"
 #include "yuzu/applets/qt_profile_select.h"
 #include "yuzu/main.h"
+#include "yuzu/util/controller_navigation.h"
 
 namespace {
 QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
@@ -45,7 +47,7 @@ QPixmap GetIcon(Common::UUID uuid) {
 }
 } // Anonymous namespace
 
-QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
+QtProfileSelectionDialog::QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent)
     : QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
     outer_layout = new QVBoxLayout;
 
@@ -65,6 +67,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
     tree_view = new QTreeView;
     item_model = new QStandardItemModel(tree_view);
     tree_view->setModel(item_model);
+    controller_navigation = new ControllerNavigation(hid_core, this);
 
     tree_view->setAlternatingRowColors(true);
     tree_view->setSelectionMode(QHeaderView::SingleSelection);
@@ -91,6 +94,14 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
     scroll_area->setLayout(layout);
 
     connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
+    connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+            [this](Qt::Key key) {
+                if (!this->isActiveWindow()) {
+                    return;
+                }
+                QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+                QCoreApplication::postEvent(tree_view, event);
+            });
 
     const auto& profiles = profile_manager->GetAllUsers();
     for (const auto& user : profiles) {
@@ -113,7 +124,9 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
     resize(550, 400);
 }
 
-QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
+QtProfileSelectionDialog::~QtProfileSelectionDialog() {
+    controller_navigation->UnloadController();
+};
 
 int QtProfileSelectionDialog::exec() {
     // Skip profile selection when there's only one.
diff --git a/src/yuzu/applets/qt_profile_select.h b/src/yuzu/applets/qt_profile_select.h
index 4e90374886..56496ed316 100644
--- a/src/yuzu/applets/qt_profile_select.h
+++ b/src/yuzu/applets/qt_profile_select.h
@@ -11,6 +11,7 @@
 #include "core/frontend/applets/profile_select.h"
 #include "core/hle/service/acc/profile_manager.h"
 
+class ControllerNavigation;
 class GMainWindow;
 class QDialogButtonBox;
 class QGraphicsScene;
@@ -20,11 +21,15 @@ class QStandardItem;
 class QStandardItemModel;
 class QVBoxLayout;
 
+namespace Core::HID {
+class HIDCore;
+} // namespace Core::HID
+
 class QtProfileSelectionDialog final : public QDialog {
     Q_OBJECT
 
 public:
-    explicit QtProfileSelectionDialog(QWidget* parent);
+    explicit QtProfileSelectionDialog(Core::HID::HIDCore& hid_core, QWidget* parent);
     ~QtProfileSelectionDialog() override;
 
     int exec() override;
@@ -51,6 +56,7 @@ private:
     QDialogButtonBox* buttons;
 
     std::unique_ptr<Service::Account::ProfileManager> profile_manager;
+    ControllerNavigation* controller_navigation = nullptr;
 };
 
 class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 2af95dbe56..1a5e41588f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -17,6 +17,7 @@
 #include <fmt/format.h>
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "yuzu/compatibility_list.h"
@@ -25,6 +26,7 @@
 #include "yuzu/game_list_worker.h"
 #include "yuzu/main.h"
 #include "yuzu/uisettings.h"
+#include "yuzu/util/controller_navigation.h"
 
 GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist, QObject* parent)
     : QObject(parent), gamelist{gamelist} {}
@@ -312,6 +314,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
     this->main_window = parent;
     layout = new QVBoxLayout;
     tree_view = new QTreeView;
+    controller_navigation = new ControllerNavigation(system.HIDCore(), this);
     search_field = new GameListSearchField(this);
     item_model = new QStandardItemModel(tree_view);
     tree_view->setModel(item_model);
@@ -341,6 +344,18 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
     connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
     connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
     connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
+    connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+            [this](Qt::Key key) {
+                // Avoid pressing buttons while playing
+                if (system.IsPoweredOn()) {
+                    return;
+                }
+                if (!this->isActiveWindow()) {
+                    return;
+                }
+                QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+                QCoreApplication::postEvent(tree_view, event);
+            });
 
     // We must register all custom types with the Qt Automoc system so that we are able to use
     // it with signals/slots. In this case, QList falls under the umbrells of custom types.
@@ -353,7 +368,12 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
     setLayout(layout);
 }
 
+void GameList::UnloadController() {
+    controller_navigation->UnloadController();
+}
+
 GameList::~GameList() {
+    UnloadController();
     emit ShouldCancelWorker();
 }
 
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 675469e669..a94ea14777 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -24,6 +24,7 @@
 #include "uisettings.h"
 #include "yuzu/compatibility_list.h"
 
+class ControllerNavigation;
 class GameListWorker;
 class GameListSearchField;
 class GameListDir;
@@ -88,6 +89,9 @@ public:
     void SaveInterfaceLayout();
     void LoadInterfaceLayout();
 
+    /// Disables events from the emulated controller
+    void UnloadController();
+
     static const QStringList supported_file_extensions;
 
 signals:
@@ -143,6 +147,7 @@ private:
     QStandardItemModel* item_model = nullptr;
     GameListWorker* current_worker = nullptr;
     QFileSystemWatcher* watcher = nullptr;
+    ControllerNavigation* controller_navigation = nullptr;
     CompatibilityList compatibility_list;
 
     friend class GameListSearchField;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 9bd0db10ac..f266fd9632 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -449,7 +449,7 @@ void GMainWindow::ControllerSelectorReconfigureControllers(
 }
 
 void GMainWindow::ProfileSelectorSelectProfile() {
-    QtProfileSelectionDialog dialog(this);
+    QtProfileSelectionDialog dialog(system->HIDCore(), this);
     dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
                           Qt::WindowTitleHint | Qt::WindowSystemMenuHint |
                           Qt::WindowCloseButtonHint);
@@ -1346,7 +1346,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
 }
 
 void GMainWindow::SelectAndSetCurrentUser() {
-    QtProfileSelectionDialog dialog(this);
+    QtProfileSelectionDialog dialog(system->HIDCore(), this);
     dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
                           Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
     dialog.setWindowModality(Qt::WindowModal);
@@ -1608,7 +1608,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
         if (has_user_save) {
             // User save data
             const auto select_profile = [this] {
-                QtProfileSelectionDialog dialog(this);
+                QtProfileSelectionDialog dialog(system->HIDCore(), this);
                 dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
                                       Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
                 dialog.setWindowModality(Qt::WindowModal);
@@ -3376,7 +3376,10 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
     UpdateUISettings();
     game_list->SaveInterfaceLayout();
     hotkey_registry.SaveHotkeys();
+
+    // Unload controllers early
     controller_dialog->UnloadController();
+    game_list->UnloadController();
     system->HIDCore().UnloadInputDevices();
 
     // Shutdown session if the emu thread is active...
diff --git a/src/yuzu/util/controller_navigation.cpp b/src/yuzu/util/controller_navigation.cpp
new file mode 100644
index 0000000000..86fb28b9fa
--- /dev/null
+++ b/src/yuzu/util/controller_navigation.cpp
@@ -0,0 +1,177 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#include "common/settings_input.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "yuzu/util/controller_navigation.h"
+
+ControllerNavigation::ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent) {
+    player1_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
+    handheld_controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
+    Core::HID::ControllerUpdateCallback engine_callback{
+        .on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
+        .is_npad_service = false,
+    };
+    player1_callback_key = player1_controller->SetCallback(engine_callback);
+    handheld_callback_key = handheld_controller->SetCallback(engine_callback);
+    is_controller_set = true;
+}
+
+ControllerNavigation::~ControllerNavigation() {
+    UnloadController();
+}
+
+void ControllerNavigation::UnloadController() {
+    if (is_controller_set) {
+        player1_controller->DeleteCallback(player1_callback_key);
+        handheld_controller->DeleteCallback(handheld_callback_key);
+        is_controller_set = false;
+    }
+}
+
+void ControllerNavigation::TriggerButton(Settings::NativeButton::Values native_button,
+                                         Qt::Key key) {
+    if (button_values[native_button].value && !button_values[native_button].locked) {
+        emit TriggerKeyboardEvent(key);
+    }
+}
+
+void ControllerNavigation::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
+    std::lock_guard lock{mutex};
+    if (type == Core::HID::ControllerTriggerType::Button) {
+        ControllerUpdateButton();
+        return;
+    }
+
+    if (type == Core::HID::ControllerTriggerType::Stick) {
+        ControllerUpdateStick();
+        return;
+    }
+}
+
+void ControllerNavigation::ControllerUpdateButton() {
+    const auto controller_type = player1_controller->GetNpadStyleIndex();
+    const auto& player1_buttons = player1_controller->GetButtonsValues();
+    const auto& handheld_buttons = handheld_controller->GetButtonsValues();
+
+    for (std::size_t i = 0; i < player1_buttons.size(); ++i) {
+        const bool button = player1_buttons[i].value || handheld_buttons[i].value;
+        // Trigger only once
+        button_values[i].locked = button == button_values[i].value;
+        button_values[i].value = button;
+    }
+
+    switch (controller_type) {
+    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::JoyconDual:
+    case Core::HID::NpadStyleIndex::Handheld:
+    case Core::HID::NpadStyleIndex::GameCube:
+        TriggerButton(Settings::NativeButton::A, Qt::Key_Enter);
+        TriggerButton(Settings::NativeButton::B, Qt::Key_Escape);
+        TriggerButton(Settings::NativeButton::DDown, Qt::Key_Down);
+        TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Left);
+        TriggerButton(Settings::NativeButton::DRight, Qt::Key_Right);
+        TriggerButton(Settings::NativeButton::DUp, Qt::Key_Up);
+        break;
+    case Core::HID::NpadStyleIndex::JoyconLeft:
+        TriggerButton(Settings::NativeButton::DDown, Qt::Key_Enter);
+        TriggerButton(Settings::NativeButton::DLeft, Qt::Key_Escape);
+        break;
+    case Core::HID::NpadStyleIndex::JoyconRight:
+        TriggerButton(Settings::NativeButton::X, Qt::Key_Enter);
+        TriggerButton(Settings::NativeButton::A, Qt::Key_Escape);
+        break;
+    default:
+        break;
+    }
+}
+
+void ControllerNavigation::ControllerUpdateStick() {
+    const auto controller_type = player1_controller->GetNpadStyleIndex();
+    const auto& player1_sticks = player1_controller->GetSticksValues();
+    const auto& handheld_sticks = player1_controller->GetSticksValues();
+    bool update = false;
+
+    for (std::size_t i = 0; i < player1_sticks.size(); ++i) {
+        const Common::Input::StickStatus stick{
+            .left = player1_sticks[i].left || handheld_sticks[i].left,
+            .right = player1_sticks[i].right || handheld_sticks[i].right,
+            .up = player1_sticks[i].up || handheld_sticks[i].up,
+            .down = player1_sticks[i].down || handheld_sticks[i].down,
+        };
+        // Trigger only once
+        if (stick.down != stick_values[i].down || stick.left != stick_values[i].left ||
+            stick.right != stick_values[i].right || stick.up != stick_values[i].up) {
+            update = true;
+        }
+        stick_values[i] = stick;
+    }
+
+    if (!update) {
+        return;
+    }
+
+    switch (controller_type) {
+    case Core::HID::NpadStyleIndex::ProController:
+    case Core::HID::NpadStyleIndex::JoyconDual:
+    case Core::HID::NpadStyleIndex::Handheld:
+    case Core::HID::NpadStyleIndex::GameCube:
+        if (stick_values[Settings::NativeAnalog::LStick].down) {
+            emit TriggerKeyboardEvent(Qt::Key_Down);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].left) {
+            emit TriggerKeyboardEvent(Qt::Key_Left);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].right) {
+            emit TriggerKeyboardEvent(Qt::Key_Right);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].up) {
+            emit TriggerKeyboardEvent(Qt::Key_Up);
+            return;
+        }
+        break;
+    case Core::HID::NpadStyleIndex::JoyconLeft:
+        if (stick_values[Settings::NativeAnalog::LStick].left) {
+            emit TriggerKeyboardEvent(Qt::Key_Down);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].up) {
+            emit TriggerKeyboardEvent(Qt::Key_Left);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].down) {
+            emit TriggerKeyboardEvent(Qt::Key_Right);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::LStick].right) {
+            emit TriggerKeyboardEvent(Qt::Key_Up);
+            return;
+        }
+        break;
+    case Core::HID::NpadStyleIndex::JoyconRight:
+        if (stick_values[Settings::NativeAnalog::RStick].right) {
+            emit TriggerKeyboardEvent(Qt::Key_Down);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::RStick].down) {
+            emit TriggerKeyboardEvent(Qt::Key_Left);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::RStick].up) {
+            emit TriggerKeyboardEvent(Qt::Key_Right);
+            return;
+        }
+        if (stick_values[Settings::NativeAnalog::RStick].left) {
+            emit TriggerKeyboardEvent(Qt::Key_Up);
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+}
diff --git a/src/yuzu/util/controller_navigation.h b/src/yuzu/util/controller_navigation.h
new file mode 100644
index 0000000000..7c616a0889
--- /dev/null
+++ b/src/yuzu/util/controller_navigation.h
@@ -0,0 +1,51 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included
+
+#pragma once
+
+#include <QKeyEvent>
+#include <QObject>
+
+#include "common/input.h"
+#include "common/settings_input.h"
+
+namespace Core::HID {
+using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
+using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
+enum class ControllerTriggerType;
+class EmulatedController;
+class HIDCore;
+} // namespace Core::HID
+
+class ControllerNavigation : public QObject {
+    Q_OBJECT
+
+public:
+    explicit ControllerNavigation(Core::HID::HIDCore& hid_core, QWidget* parent = nullptr);
+    ~ControllerNavigation();
+
+    /// Disables events from the emulated controller
+    void UnloadController();
+
+signals:
+    void TriggerKeyboardEvent(Qt::Key key);
+
+private:
+    void TriggerButton(Settings::NativeButton::Values native_button, Qt::Key key);
+    void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
+
+    void ControllerUpdateButton();
+
+    void ControllerUpdateStick();
+
+    Core::HID::ButtonValues button_values{};
+    Core::HID::SticksValues stick_values{};
+
+    int player1_callback_key{};
+    int handheld_callback_key{};
+    bool is_controller_set{};
+    mutable std::mutex mutex;
+    Core::HID::EmulatedController* player1_controller;
+    Core::HID::EmulatedController* handheld_controller;
+};