diff --git a/src/duckstation-qt/controllerglobalsettingswidget.cpp b/src/duckstation-qt/controllerglobalsettingswidget.cpp
index d2bc7c5a5..5d45a2f84 100644
--- a/src/duckstation-qt/controllerglobalsettingswidget.cpp
+++ b/src/duckstation-qt/controllerglobalsettingswidget.cpp
@@ -32,7 +32,8 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent,
   m_ui.enableXInputSource->setEnabled(false);
   m_ui.enableRawInput->setEnabled(false);
 #endif
-  SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false);
+  ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping",
+                                                              false);
   SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.multitapMode, "ControllerPorts", "MultitapMode",
                                                &Settings::ParseMultitapModeName, &Settings::GetMultitapModeName,
                                                Settings::DEFAULT_MULTITAP_MODE);
diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp
index d31b701ad..a87973257 100644
--- a/src/duckstation-qt/gamelistmodel.cpp
+++ b/src/duckstation-qt/gamelistmodel.cpp
@@ -114,11 +114,11 @@ const char* GameListModel::getColumnName(Column col)
   return s_column_names[static_cast<int>(col)];
 }
 
-GameListModel::GameListModel(QObject* parent /* = nullptr */)
-  : QAbstractTableModel(parent), m_cover_pixmap_cache(MIN_COVER_CACHE_SIZE)
+GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */)
+  : QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles)
 {
   loadCommonImages();
-  setCoverScale(1.0f);
+  setCoverScale(cover_scale);
   setColumnDisplayNames();
 }
 GameListModel::~GameListModel() = default;
@@ -132,6 +132,8 @@ void GameListModel::setCoverScale(float scale)
   m_cover_scale = scale;
   m_loading_pixmap = QPixmap(getCoverArtWidth(), getCoverArtHeight());
   m_loading_pixmap.fill(QColor(0, 0, 0, 0));
+
+  emit coverScaleChanged();
 }
 
 void GameListModel::refreshCovers()
@@ -150,9 +152,9 @@ void GameListModel::updateCacheSize(int width, int height)
   m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE)));
 }
 
-void GameListModel::reloadCommonImages()
+void GameListModel::reloadThemeSpecificImages()
 {
-  loadCommonImages();
+  loadThemeSpecificImages();
   refresh();
 }
 
@@ -585,17 +587,24 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
   }
 }
 
-void GameListModel::loadCommonImages()
+void GameListModel::loadThemeSpecificImages()
 {
   for (u32 i = 0; i < static_cast<u32>(GameList::EntryType::Count); i++)
     m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast<GameList::EntryType>(i)).pixmap(QSize(24, 24));
 
   for (u32 i = 0; i < static_cast<u32>(DiscRegion::Count); i++)
     m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast<DiscRegion>(i)).pixmap(42, 30);
+}
+
+void GameListModel::loadCommonImages()
+{
+  loadThemeSpecificImages();
 
   for (int i = 0; i < static_cast<int>(GameDatabase::CompatibilityRating::Count); i++)
+  {
     m_compatibility_pixmaps[i] =
       QtUtils::GetIconForCompatibility(static_cast<GameDatabase::CompatibilityRating>(i)).pixmap(96, 24);
+  }
 
   m_placeholder_pixmap.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath()));
 }
diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h
index 62c71a838..ac5ef1fd3 100644
--- a/src/duckstation-qt/gamelistmodel.h
+++ b/src/duckstation-qt/gamelistmodel.h
@@ -45,7 +45,7 @@ public:
   static std::optional<Column> getColumnIdForName(std::string_view name);
   static const char* getColumnName(Column col);
 
-  GameListModel(QObject* parent = nullptr);
+  GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr);
   ~GameListModel();
 
   int rowCount(const QModelIndex& parent = QModelIndex()) const override;
@@ -56,6 +56,7 @@ public:
   ALWAYS_INLINE const QString& getColumnDisplayName(int column) { return m_column_display_names[column]; }
 
   void refresh();
+  void reloadThemeSpecificImages();
 
   bool titlesLessThan(int left_row, int right_row) const;
 
@@ -71,10 +72,13 @@ public:
   int getCoverArtSpacing() const;
   void refreshCovers();
   void updateCacheSize(int width, int height);
-  void reloadCommonImages();
+
+Q_SIGNALS:
+  void coverScaleChanged();
 
 private:
   void loadCommonImages();
+  void loadThemeSpecificImages();
   void setColumnDisplayNames();
   void loadOrGenerateCover(const GameList::Entry* ge);
   void invalidateCoverForPath(const std::string& path);
diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp
index d7c12fb90..fc1613cd8 100644
--- a/src/duckstation-qt/gamelistwidget.cpp
+++ b/src/duckstation-qt/gamelistwidget.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #include "gamelistwidget.h"
@@ -20,6 +20,7 @@
 #include <QtGui/QWheelEvent>
 #include <QtWidgets/QHeaderView>
 #include <QtWidgets/QMenu>
+#include <QtWidgets/QScrollBar>
 
 static constexpr float MIN_SCALE = 0.1f;
 static constexpr float MAX_SCALE = 2.0f;
@@ -91,9 +92,9 @@ GameListWidget::~GameListWidget() = default;
 
 void GameListWidget::initialize()
 {
-  m_model = new GameListModel(this);
-  m_model->setCoverScale(Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f));
-  m_model->setShowCoverTitles(Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true));
+  const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
+  const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
+  m_model = new GameListModel(cover_scale, show_cover_titles, this);
   m_model->updateCacheSize(width(), height());
 
   m_sort_model = new GameListSortModel(m_model);
@@ -162,17 +163,16 @@ void GameListWidget::initialize()
   m_list_view = new GameListGridListView(m_ui.stack);
   m_list_view->setModel(m_sort_model);
   m_list_view->setModelColumn(GameListModel::Column_Cover);
-  m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
+  m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
   m_list_view->setViewMode(QListView::IconMode);
   m_list_view->setResizeMode(QListView::Adjust);
   m_list_view->setUniformItemSizes(true);
   m_list_view->setItemAlignment(Qt::AlignHCenter);
   m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
   m_list_view->setFrameStyle(QFrame::NoFrame);
-  m_list_view->setSpacing(m_model->getCoverArtSpacing());
   m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
-
-  updateListFont();
+  m_list_view->verticalScrollBar()->setSingleStep(15);
+  onCoverScaleChanged();
 
   connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
           &GameListWidget::onSelectionModelCurrentChanged);
@@ -180,6 +180,7 @@ void GameListWidget::initialize()
   connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
   connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
   connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
+  connect(m_model, &GameListModel::coverScaleChanged, this, &GameListWidget::onCoverScaleChanged);
 
   m_ui.stack->insertWidget(1, m_list_view);
 
@@ -237,6 +238,11 @@ void GameListWidget::cancelRefresh()
   AssertMsg(!m_refresh_thread, "Game list thread should be unreferenced by now");
 }
 
+void GameListWidget::reloadThemeSpecificImages()
+{
+  m_model->reloadThemeSpecificImages();
+}
+
 void GameListWidget::onRefreshProgress(const QString& status, int current, int total)
 {
   // switch away from the placeholder while we scan, in case we find anything
@@ -326,14 +332,23 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
   saveTableViewColumnSortSettings();
 }
 
+void GameListWidget::onCoverScaleChanged()
+{
+  m_model->updateCacheSize(width(), height());
+
+  m_list_view->setSpacing(m_model->getCoverArtSpacing());
+
+  QFont font;
+  font.setPointSizeF(16.0f * m_model->getCoverScale());
+  m_list_view->setFont(font);
+}
+
 void GameListWidget::listZoom(float delta)
 {
   const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE);
   Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
   Host::CommitBaseSettingChanges();
   m_model->setCoverScale(new_scale);
-  m_model->updateCacheSize(width(), height());
-  updateListFont();
   updateToolbar();
 
   m_model->refresh();
@@ -356,8 +371,6 @@ void GameListWidget::gridIntScale(int int_scale)
   Host::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
   Host::CommitBaseSettingChanges();
   m_model->setCoverScale(new_scale);
-  m_model->updateCacheSize(width(), height());
-  updateListFont();
   updateToolbar();
 
   m_model->refresh();
@@ -416,13 +429,6 @@ void GameListWidget::setShowCoverTitles(bool enabled)
   emit layoutChange();
 }
 
-void GameListWidget::updateListFont()
-{
-  QFont font;
-  font.setPointSizeF(16.0f * m_model->getCoverScale());
-  m_list_view->setFont(font);
-}
-
 void GameListWidget::updateToolbar()
 {
   const bool grid_view = isShowingGameGrid();
@@ -473,11 +479,6 @@ void GameListWidget::resizeTableViewColumnsToFit()
                                                    });
 }
 
-void GameListWidget::reloadCommonImages()
-{
-  m_model->reloadCommonImages();
-}
-
 static TinyString getColumnVisibilitySettingsKeyName(int column)
 {
   return TinyString::FromFormat("Show%s", GameListModel::getColumnName(static_cast<GameListModel::Column>(column)));
diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h
index 42339cb10..979331ee7 100644
--- a/src/duckstation-qt/gamelistwidget.h
+++ b/src/duckstation-qt/gamelistwidget.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
+// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
 
 #pragma once
@@ -43,10 +43,10 @@ public:
 
   void initialize();
   void resizeTableViewColumnsToFit();
-  void reloadCommonImages();
 
   void refresh(bool invalidate_cache);
   void cancelRefresh();
+  void reloadThemeSpecificImages();
 
   bool isShowingGameList() const;
   bool isShowingGameGrid() const;
@@ -76,6 +76,7 @@ private Q_SLOTS:
   void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder);
   void onListViewItemActivated(const QModelIndex& index);
   void onListViewContextMenuRequested(const QPoint& point);
+  void onCoverScaleChanged();
 
 public Q_SLOTS:
   void showGameList();
@@ -96,7 +97,6 @@ private:
   void loadTableViewColumnSortSettings();
   void saveTableViewColumnSortSettings();
   void listZoom(float delta);
-  void updateListFont();
   void updateToolbar();
 
   Ui::GameListWidget m_ui;
diff --git a/src/duckstation-qt/inputbindingdialog.cpp b/src/duckstation-qt/inputbindingdialog.cpp
index f80225fb7..bc64b6581 100644
--- a/src/duckstation-qt/inputbindingdialog.cpp
+++ b/src/duckstation-qt/inputbindingdialog.cpp
@@ -135,7 +135,7 @@ void InputBindingDialog::startListeningForInput(u32 timeout_in_seconds)
 {
   m_value_ranges.clear();
   m_new_bindings.clear();
-  m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled();
+  m_mouse_mapping_enabled = InputBindingWidget::isMouseMappingEnabled(m_sif);
   m_input_listen_start_position = QCursor::pos();
   m_input_listen_timer = new QTimer(this);
   m_input_listen_timer->setSingleShot(false);
diff --git a/src/duckstation-qt/inputbindingwidgets.cpp b/src/duckstation-qt/inputbindingwidgets.cpp
index 3a85c71fc..e5829e993 100644
--- a/src/duckstation-qt/inputbindingwidgets.cpp
+++ b/src/duckstation-qt/inputbindingwidgets.cpp
@@ -42,9 +42,10 @@ InputBindingWidget::~InputBindingWidget()
   Q_ASSERT(!isListeningForInput());
 }
 
-bool InputBindingWidget::isMouseMappingEnabled()
+bool InputBindingWidget::isMouseMappingEnabled(SettingsInterface* sif)
 {
-  return Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
+  return sif ? sif->GetBoolValue("UI", "EnableMouseMapping", false) :
+               Host::GetBaseBoolSettingValue("UI", "EnableMouseMapping", false);
 }
 
 void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
@@ -287,7 +288,7 @@ void InputBindingWidget::startListeningForInput(u32 timeout_in_seconds)
 {
   m_value_ranges.clear();
   m_new_bindings.clear();
-  m_mouse_mapping_enabled = isMouseMappingEnabled();
+  m_mouse_mapping_enabled = isMouseMappingEnabled(m_sif);
   m_input_listen_start_position = QCursor::pos();
   m_input_listen_timer = new QTimer(this);
   m_input_listen_timer->setSingleShot(false);
diff --git a/src/duckstation-qt/inputbindingwidgets.h b/src/duckstation-qt/inputbindingwidgets.h
index 57b0169ea..ae11b43b7 100644
--- a/src/duckstation-qt/inputbindingwidgets.h
+++ b/src/duckstation-qt/inputbindingwidgets.h
@@ -22,7 +22,7 @@ public:
                      std::string section_name, std::string key_name);
   ~InputBindingWidget();
 
-  static bool isMouseMappingEnabled();
+  static bool isMouseMappingEnabled(SettingsInterface* sif);
 
   void initialize(SettingsInterface* sif, InputBindingInfo::Type bind_type, std::string section_name,
                   std::string key_name);
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index 87643e22f..6655020d8 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -1417,13 +1417,25 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
 
 void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
 {
-  QString filename = QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(),
-                                                  tr("All Cover Image Types (*.jpg *.jpeg *.png)"));
+  const QString filename = QDir::toNativeSeparators(QFileDialog::getOpenFileName(
+    this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png *.webp)")));
   if (filename.isEmpty())
     return;
 
-  if (!GameList::GetCoverImagePathForEntry(entry).empty())
+  const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(entry));
+  const QString new_filename =
+    QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData(), false));
+  if (new_filename.isEmpty())
+    return;
+
+  if (!old_filename.isEmpty())
   {
+    if (QFileInfo(old_filename) == QFileInfo(filename))
+    {
+      QMessageBox::critical(this, tr("Copy Error"), tr("You must select a different file to the current cover image."));
+      return;
+    }
+
     if (QMessageBox::question(this, tr("Cover Already Exists"),
                               tr("A cover image for this game already exists, do you wish to replace it?"),
                               QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
@@ -1432,23 +1444,21 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
     }
   }
 
-  QString new_filename =
-    QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toStdString().c_str(), false));
-  if (new_filename.isEmpty())
-    return;
-
   if (QFile::exists(new_filename) && !QFile::remove(new_filename))
   {
     QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename));
     return;
   }
-
   if (!QFile::copy(filename, new_filename))
   {
     QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename));
     return;
   }
-
+  if (!old_filename.isEmpty() && old_filename != new_filename && !QFile::remove(old_filename))
+  {
+    QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove '%1'").arg(old_filename));
+    return;
+  }
   m_game_list_widget->refreshGridCovers();
 }
 
@@ -2068,7 +2078,12 @@ void MainWindow::updateTheme()
 {
   updateApplicationTheme();
   updateMenuSelectedTheme();
-  m_game_list_widget->reloadCommonImages();
+  reloadThemeSpecificImages();
+}
+
+void MainWindow::reloadThemeSpecificImages()
+{
+  m_game_list_widget->reloadThemeSpecificImages();
 }
 
 void MainWindow::setStyleFromSettings()
@@ -2405,6 +2420,12 @@ void MainWindow::changeEvent(QEvent* event)
       g_emu_thread->redrawDisplayWindow();
   }
 
+  if (event->type() == QEvent::StyleChange)
+  {
+    setIconThemeFromSettings();
+    reloadThemeSpecificImages();
+  }
+
   QMainWindow::changeEvent(event);
 }
 
diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h
index ad63c0d42..ecd7bdbe9 100644
--- a/src/duckstation-qt/mainwindow.h
+++ b/src/duckstation-qt/mainwindow.h
@@ -239,6 +239,7 @@ private:
   void clearGameListEntryPlayTime(const GameList::Entry* entry);
   void setTheme(const QString& theme);
   void updateTheme();
+  void reloadThemeSpecificImages();
   void recreate();
 
   void registerForDeviceNotifications();
diff --git a/src/duckstation-qt/settingwidgetbinder.h b/src/duckstation-qt/settingwidgetbinder.h
index 707fd1a6f..3bba4e860 100644
--- a/src/duckstation-qt/settingwidgetbinder.h
+++ b/src/duckstation-qt/settingwidgetbinder.h
@@ -11,6 +11,7 @@
 #include "core/settings.h"
 
 #include "common/assert.h"
+#include "common/file_system.h"
 #include "common/path.h"
 
 #include <QtCore/QtCore>
@@ -22,6 +23,7 @@
 #include <QtWidgets/QFileDialog>
 #include <QtWidgets/QLineEdit>
 #include <QtWidgets/QMenu>
+#include <QtWidgets/QMessageBox>
 #include <QtWidgets/QSlider>
 #include <QtWidgets/QSpinBox>
 #include <optional>
@@ -1030,22 +1032,20 @@ static void BindWidgetToEnumSetting(SettingsInterface* sif, WidgetType* widget,
   }
 }
 
-template<typename WidgetType>
-static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget, QAbstractButton* browse_button,
-                                      QAbstractButton* open_button, QAbstractButton* reset_button, std::string section,
-                                      std::string key, std::string default_value)
+static inline void BindWidgetToFolderSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
+                                             QAbstractButton* open_button, QAbstractButton* reset_button,
+                                             std::string section, std::string key, std::string default_value,
+                                             bool use_relative = true)
 {
-  using Accessor = SettingAccessor<WidgetType>;
+  using Accessor = SettingAccessor<QLineEdit>;
 
   std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
   if (current_path.empty())
     current_path = default_value;
-  else if (!Path::IsAbsolute(current_path))
-    current_path = Path::Combine(EmuFolders::DataRoot, current_path);
-
+  else if (use_relative && !Path::IsAbsolute(current_path))
+    current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path));
   const QString value(QString::fromStdString(current_path));
   Accessor::setStringValue(widget, value);
-
   // if we're doing per-game settings, disable the widget, we only allow folder changes in the base config
   if (sif)
   {
@@ -1057,32 +1057,65 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget
     return;
   }
 
-  Accessor::connectValueChanged(widget, [widget, section = std::move(section), key = std::move(key)]() {
-    const std::string new_value(Accessor::getStringValue(widget).toStdString());
+  auto value_changed = [widget, section = std::move(section), key = std::move(key), default_value, use_relative]() {
+    const std::string new_value(widget->text().toStdString());
     if (!new_value.empty())
     {
-      std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot));
-      Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str());
+      if (FileSystem::DirectoryExists(new_value.c_str()) ||
+          QMessageBox::question(
+            QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Confirm Folder"),
+            qApp
+              ->translate(
+                "SettingWidgetBinder",
+                "The chosen directory does not currently exist:\n\n%1\n\nDo you want to create this directory?")
+              .arg(QString::fromStdString(new_value)),
+            QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
+      {
+        if (use_relative)
+        {
+          const std::string relative_path(Path::MakeRelative(new_value, EmuFolders::DataRoot));
+          Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str());
+        }
+        else
+        {
+          Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.c_str());
+        }
+
+        Host::CommitBaseSettingChanges();
+        g_emu_thread->updateEmuFolders();
+        return;
+      }
     }
     else
     {
-      Host::DeleteBaseSettingValue(section.c_str(), key.c_str());
+      QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"),
+                            qApp->translate("SettingWidgetBinder", "Folder path cannot be empty."));
     }
 
-    Host::CommitBaseSettingChanges();
-    g_emu_thread->updateEmuFolders();
-  });
+    // reset to old value
+    std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
+    if (current_path.empty())
+      current_path = default_value;
+    else if (use_relative && !Path::IsAbsolute(current_path))
+      current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path));
+
+    widget->setText(QString::fromStdString(current_path));
+  };
 
   if (browse_button)
   {
-    QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key]() {
+    QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key, value_changed]() {
       const QString path(QDir::toNativeSeparators(QFileDialog::getExistingDirectory(
         QtUtils::GetRootWidget(widget),
+        // It seems that the latter half should show the types of folders that can be selected within Settings ->
+        // Folders, but right now it's broken. It would be best for localization purposes to duplicate this into
+        // multiple lines, each per type of folder.
         qApp->translate("SettingWidgetBinder", "Select folder for %1").arg(QString::fromStdString(key)))));
       if (path.isEmpty())
         return;
 
-      Accessor::setStringValue(widget, path);
+      widget->setText(path);
+      value_changed();
     });
   }
   if (open_button)
@@ -1096,9 +1129,12 @@ static void BindWidgetToFolderSetting(SettingsInterface* sif, WidgetType* widget
   if (reset_button)
   {
     QObject::connect(reset_button, &QAbstractButton::clicked, reset_button,
-                     [widget, default_value = std::move(default_value)]() {
-                       Accessor::setStringValue(widget, QString::fromStdString(default_value));
+                     [widget, default_value = std::move(default_value), value_changed]() {
+                       widget->setText(QString::fromStdString(default_value));
+                       value_changed();
                      });
   }
+
+  widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed));
 }
 } // namespace SettingWidgetBinder