diff --git a/src/core/settings.h b/src/core/settings.h
index a324530bdb..d849dded38 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -181,12 +181,13 @@ struct Values {
     std::string motion_device;
     std::string udp_input_servers;
 
-    bool emulate_analog_keyboard;
-
+    bool mouse_panning;
+    float mouse_panning_sensitivity;
     bool mouse_enabled;
     std::string mouse_device;
     MouseButtonsRaw mouse_buttons;
 
+    bool emulate_analog_keyboard;
     bool keyboard_enabled;
     KeyboardKeysRaw keyboard_keys;
     KeyboardModsRaw keyboard_mods;
diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp
index 10786a5413..67a584d53d 100644
--- a/src/input_common/mouse/mouse_input.cpp
+++ b/src/input_common/mouse/mouse_input.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2+
 // Refer to the license.txt file included.
 
+#include "core/settings.h"
 #include "input_common/mouse/mouse_input.h"
 
 namespace MouseInput {
@@ -36,6 +37,9 @@ void Mouse::UpdateThread() {
         if (configuring) {
             UpdateYuzuSettings();
         }
+        if (mouse_panning_timout++ > 8) {
+            StopPanning();
+        }
         std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
     }
 }
@@ -65,8 +69,34 @@ void Mouse::PressButton(int x, int y, int button_) {
     mouse_info[button_index].data.pressed = true;
 }
 
-void Mouse::MouseMove(int x, int y) {
+void Mouse::StopPanning() {
     for (MouseInfo& info : mouse_info) {
+        if (Settings::values.mouse_panning) {
+            info.data.axis = {};
+            info.tilt_speed = 0;
+            info.last_mouse_change = {};
+        }
+    }
+}
+
+void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
+    for (MouseInfo& info : mouse_info) {
+        if (Settings::values.mouse_panning) {
+            const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y);
+            mouse_panning_timout = 0;
+
+            if (mouse_change.y == 0 && mouse_change.x == 0) {
+                continue;
+            }
+
+            info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f);
+            info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
+                              static_cast<int>(16 * -info.last_mouse_change.y)};
+            info.tilt_direction = info.last_mouse_change;
+            info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
+            continue;
+        }
+
         if (info.data.pressed) {
             const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin;
             const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position;
diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h
index 58803c1bf2..46aa676c15 100644
--- a/src/input_common/mouse/mouse_input.h
+++ b/src/input_common/mouse/mouse_input.h
@@ -57,8 +57,10 @@ public:
      * Signals that mouse has moved.
      * @param x the x-coordinate of the cursor
      * @param y the y-coordinate of the cursor
+     * @param center_x the x-coordinate of the middle of the screen
+     * @param center_y the y-coordinate of the middle of the screen
      */
-    void MouseMove(int x, int y);
+    void MouseMove(int x, int y, int center_x, int center_y);
 
     /**
      * Signals that a motion sensor tilt has ended.
@@ -74,11 +76,13 @@ public:
 private:
     void UpdateThread();
     void UpdateYuzuSettings();
+    void StopPanning();
 
     struct MouseInfo {
         InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
         Common::Vec2<int> mouse_origin;
         Common::Vec2<int> last_mouse_position;
+        Common::Vec2<float> last_mouse_change;
         bool is_tilting = false;
         float sensitivity{0.120f};
 
@@ -94,5 +98,6 @@ private:
     Common::SPSCQueue<MouseStatus> mouse_queue;
     bool configuring{false};
     bool update_thread_running{true};
+    int mouse_panning_timout{};
 };
 } // namespace MouseInput
diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp
index 3d799b2937..bb56787ee3 100644
--- a/src/input_common/mouse/mouse_poller.cpp
+++ b/src/input_common/mouse/mouse_poller.cpp
@@ -6,6 +6,7 @@
 #include <utility>
 
 #include "common/threadsafe_queue.h"
+#include "core/settings.h"
 #include "input_common/mouse/mouse_input.h"
 #include "input_common/mouse/mouse_poller.h"
 
@@ -71,7 +72,7 @@ public:
         std::lock_guard lock{mutex};
         const auto axis_value =
             static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
-        return axis_value / (100.0f * range);
+        return axis_value * Settings::values.mouse_panning_sensitivity / (100.0f * range);
     }
 
     std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index ffdf34a4a0..d9a3035cb7 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -405,12 +405,17 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
     if (event->source() == Qt::MouseEventSynthesizedBySystem) {
         return;
     }
-
     auto pos = event->pos();
     const auto [x, y] = ScaleTouch(pos);
-    input_subsystem->GetMouse()->MouseMove(x, y);
+    const int center_x = width() / 2;
+    const int center_y = height() / 2;
+    input_subsystem->GetMouse()->MouseMove(x, y, center_x, center_y);
     this->TouchMoved(x, y, 0);
 
+    if (Settings::values.mouse_panning) {
+        QCursor::setPos(mapToGlobal({center_x, center_y}));
+    }
+
     emit MouseActivity();
 }
 
@@ -714,6 +719,11 @@ void GRenderWindow::showEvent(QShowEvent* event) {
 
 bool GRenderWindow::eventFilter(QObject* object, QEvent* event) {
     if (event->type() == QEvent::HoverMove) {
+        if (Settings::values.mouse_panning) {
+            auto* hover_event = static_cast<QMouseEvent*>(event);
+            mouseMoveEvent(hover_event);
+            return false;
+        }
         emit MouseActivity();
     }
     return false;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 8d85a1986e..8f7458119a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -220,7 +220,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
 // This must be in alphabetical order according to action name as it must have the same order as
 // UISetting::values.shortcuts, which is alphabetically ordered.
 // clang-format off
-const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
+const std::array<UISettings::Shortcut, 17> Config::default_hotkeys{{
     {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
     {QStringLiteral("Change Docked Mode"),       QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
     {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -235,6 +235,7 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
     {QStringLiteral("Restart Emulation"),        QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
     {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
     {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
+    {QStringLiteral("Toggle Mouse Panning"),     QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::ApplicationShortcut}},
     {QStringLiteral("Toggle Speed Limit"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
     {QStringLiteral("Toggle Status Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
 }};
@@ -507,6 +508,9 @@ void Config::ReadControlValues() {
 
     Settings::values.emulate_analog_keyboard =
         ReadSetting(QStringLiteral("emulate_analog_keyboard"), false).toBool();
+    Settings::values.mouse_panning = ReadSetting(QStringLiteral("mouse_panning"), false).toBool();
+    Settings::values.mouse_panning_sensitivity =
+        ReadSetting(QStringLiteral("mouse_panning_sensitivity"), 1).toFloat();
 
     ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), true);
     ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"),
@@ -1184,7 +1188,9 @@ void Config::SaveControlValues() {
     WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
     WriteSetting(QStringLiteral("emulate_analog_keyboard"),
                  Settings::values.emulate_analog_keyboard, false);
-
+    WriteSetting(QStringLiteral("mouse_panning"), Settings::values.mouse_panning, false);
+    WriteSetting(QStringLiteral("mouse_panning_sensitivity"),
+                 Settings::values.mouse_panning_sensitivity, 1.0f);
     qt_config->endGroup();
 }
 
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 8a600e19d5..949c4eb134 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,7 +42,7 @@ public:
         default_mouse_buttons;
     static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
     static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
-    static const std::array<UISettings::Shortcut, 16> default_hotkeys;
+    static const std::array<UISettings::Shortcut, 17> default_hotkeys;
 
 private:
     void Initialize(const std::string& config_name);
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 4e557bc6f2..a1a0eb676c 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -122,6 +122,9 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
     Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
     Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
     Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
+    Settings::values.mouse_panning = ui->mouse_panning->isChecked();
+    Settings::values.mouse_panning_sensitivity =
+        static_cast<float>(ui->mouse_panning_sensitivity->value());
     Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
 }
 
@@ -149,6 +152,8 @@ void ConfigureInputAdvanced::LoadConfiguration() {
     ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
     ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
     ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard);
+    ui->mouse_panning->setChecked(Settings::values.mouse_panning);
+    ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity);
     ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
 
     UpdateUIEnabled();
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index f207e5d3be..173130d8db 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2546,27 +2546,65 @@
                  </property>
                 </widget>
                </item>
-               <item row="1" column="0">
-                 <widget class="QCheckBox" name="emulate_analog_keyboard">
-                   <property name="minimumSize">
-                     <size>
-                       <width>0</width>
-                       <height>23</height>
-                     </size>
-                   </property>
-                   <property name="text">
-                     <string>Emulate Analog with Keyboard Input</string>
-                   </property>
-                 </widget>
-               </item>
-               <item row="5" column="2">
+                <item row="1" column="0">
+                  <widget class="QCheckBox" name="emulate_analog_keyboard">
+                    <property name="minimumSize">
+                      <size>
+                        <width>0</width>
+                        <height>23</height>
+                      </size>
+                    </property>
+                    <property name="text">
+                      <string>Emulate Analog with Keyboard Input</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="2" column="0">
+                  <widget class="QCheckBox" name="mouse_panning">
+                    <property name="minimumSize">
+                      <size>
+                        <width>0</width>
+                        <height>23</height>
+                      </size>
+                    </property>
+                    <property name="text">
+                      <string>Enable mouse panning</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="2" column="2">
+                    <widget class="QDoubleSpinBox" name="mouse_panning_sensitivity">
+                      <property name="toolTip">
+                        <string>Mouse sensitivity</string>
+                      </property>
+                      <property name="alignment">
+                        <set>Qt::AlignCenter</set>
+                      </property>
+                      <property name="decimals">
+                        <number>2</number>
+                      </property>
+                      <property name="minimum">
+                        <double>0.100000000000000</double>
+                      </property>
+                      <property name="maximum">
+                        <double>16.000000000000000</double>
+                      </property>
+                      <property name="singleStep">
+                        <double>0.010000000000000</double>
+                      </property>
+                      <property name="value">
+                        <double>1.000000000000000</double>
+                      </property>
+                    </widget>
+                </item>
+               <item row="6" column="2">
                 <widget class="QPushButton" name="touchscreen_advanced">
                  <property name="text">
                   <string>Advanced</string>
                  </property>
                 </widget>
                </item>
-               <item row="2" column="1">
+               <item row="3" column="1">
                 <spacer name="horizontalSpacer_8">
                  <property name="orientation">
                   <enum>Qt::Horizontal</enum>
@@ -2582,21 +2620,21 @@
                  </property>
                 </spacer>
                </item>
-               <item row="2" column="2">
+               <item row="3" column="2">
                 <widget class="QPushButton" name="mouse_advanced">
                  <property name="text">
                   <string>Advanced</string>
                  </property>
                 </widget>
                </item>
-               <item row="5" column="0">
+               <item row="6" column="0">
                 <widget class="QCheckBox" name="touchscreen_enabled">
                  <property name="text">
                   <string>Touchscreen</string>
                  </property>
                 </widget>
                </item>
-               <item row="2" column="0">
+               <item row="3" column="0">
                 <widget class="QCheckBox" name="mouse_enabled">
                  <property name="minimumSize">
                   <size>
@@ -2609,28 +2647,28 @@
                  </property>
                 </widget>
                </item>
-               <item row="7" column="0">
+               <item row="8" column="0">
                 <widget class="QLabel" name="motion_touch">
                  <property name="text">
                   <string>Motion / Touch</string>
                  </property>
                 </widget>
                </item>
-               <item row="7" column="2">
+               <item row="8" column="2">
                 <widget class="QPushButton" name="buttonMotionTouch">
                  <property name="text">
                   <string>Configure</string>
                  </property>
                 </widget>
                </item>
-               <item row="6" column="0">
+               <item row="7" column="0">
                 <widget class="QCheckBox" name="debug_enabled">
                  <property name="text">
                   <string>Debug Controller</string>
                  </property>
                 </widget>
                </item>
-               <item row="6" column="2">
+               <item row="7" column="2">
                 <widget class="QPushButton" name="debug_configure">
                  <property name="text">
                   <string>Configure</string>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ef92c25bc6..52218eb705 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -850,6 +850,16 @@ void GMainWindow::InitializeHotkeys() {
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
             &QShortcut::activated, this,
             [] { Settings::values.audio_muted = !Settings::values.audio_muted; });
+
+    connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Toggle Mouse Panning"), this),
+            &QShortcut::activated, this, [&] {
+                Settings::values.mouse_panning = !Settings::values.mouse_panning;
+                if (UISettings::values.hide_mouse || Settings::values.mouse_panning) {
+                    mouse_hide_timer.start();
+                    render_window->installEventFilter(render_window);
+                    render_window->setAttribute(Qt::WA_Hover, true);
+                }
+            });
 }
 
 void GMainWindow::SetDefaultUIGeometry() {
@@ -1197,7 +1207,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) {
     multicore_status_button->setDisabled(true);
     renderer_status_button->setDisabled(true);
 
-    if (UISettings::values.hide_mouse) {
+    if (UISettings::values.hide_mouse || Settings::values.mouse_panning) {
         mouse_hide_timer.start();
         render_window->installEventFilter(render_window);
         render_window->setAttribute(Qt::WA_Hover, true);
@@ -2359,7 +2369,7 @@ void GMainWindow::OnConfigure() {
 
     config->Save();
 
-    if (UISettings::values.hide_mouse && emulation_running) {
+    if ((UISettings::values.hide_mouse || Settings::values.mouse_panning) && emulation_running) {
         render_window->installEventFilter(render_window);
         render_window->setAttribute(Qt::WA_Hover, true);
         mouse_hide_timer.start();
@@ -2600,7 +2610,8 @@ void GMainWindow::UpdateUISettings() {
 }
 
 void GMainWindow::HideMouseCursor() {
-    if (emu_thread == nullptr || UISettings::values.hide_mouse == false) {
+    if (emu_thread == nullptr ||
+        (!UISettings::values.hide_mouse && !Settings::values.mouse_panning)) {
         mouse_hide_timer.stop();
         ShowMouseCursor();
         return;
@@ -2610,13 +2621,16 @@ void GMainWindow::HideMouseCursor() {
 
 void GMainWindow::ShowMouseCursor() {
     render_window->unsetCursor();
-    if (emu_thread != nullptr && UISettings::values.hide_mouse) {
+    if (emu_thread != nullptr &&
+        (UISettings::values.hide_mouse || Settings::values.mouse_panning)) {
         mouse_hide_timer.start();
     }
 }
 
 void GMainWindow::OnMouseActivity() {
-    ShowMouseCursor();
+    if (!Settings::values.mouse_panning) {
+        ShowMouseCursor();
+    }
 }
 
 void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 7843d51672..39841aa28f 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -30,7 +30,8 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
 
 void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0), 0);
-    input_subsystem->GetMouse()->MouseMove(x, y);
+
+    input_subsystem->GetMouse()->MouseMove(x, y, 0, 0);
 }
 
 void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {