diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt b/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt
index a6d2ff63c1462557b5c55fc9538228dc46509ef1..1ee52e8455a17440fd7b4557d1bd24d2355dfde4 100644
--- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt
+++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt
@@ -26,6 +26,7 @@ set(SOURCES
     aronTreeWidget/modal/AronTreeWidgetModal.cpp
     ColorPalettes.cpp
     SkillManagerMonitorWidgetController.cpp
+    PeriodicUpdateWidget.cpp
 )
 
 set(HEADERS
@@ -51,6 +52,7 @@ set(HEADERS
     aronTreeWidget/modal/bool/AronTreeWidgetBoolInputModalController.h
     ColorPalettes.h
     SkillManagerMonitorWidgetController.h
+    PeriodicUpdateWidget.h
 )
 
 set(GUI_UIS
diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.cpp b/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d039d0d5434121e7d1d2750ef21753ee62c8be97
--- /dev/null
+++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.cpp
@@ -0,0 +1,141 @@
+#include "PeriodicUpdateWidget.h"
+
+#include <cmath>
+
+#include <QCheckBox>
+#include <QDoubleSpinBox>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QTimer>
+
+namespace armarx
+{
+    PeriodicUpdateWidget::PeriodicUpdateWidget(double frequency, double maxFrequency)
+    {
+        setSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Fixed);
+
+        QLayout* vlayout = new QVBoxLayout();
+        QLayout* hlayout = new QHBoxLayout();
+        this->setLayout(vlayout);
+
+        const int margin = 0;
+        hlayout->setContentsMargins(margin, margin, margin, margin);
+
+        _updateButton = new QPushButton("Update", this);
+        _autoCheckBox = new QCheckBox("Auto Update", this);
+        _frequencySpinBox = new QDoubleSpinBox(this);
+        _frequencySpinBox->setValue(frequency);
+        _frequencySpinBox->setMinimum(0);
+        _frequencySpinBox->setMaximum(maxFrequency);
+        _frequencySpinBox->setSingleStep(0.5);
+        _frequencySpinBox->setSuffix(" Hz");
+
+        hlayout->addWidget(_updateButton);
+        hlayout->addWidget(_autoCheckBox);
+        hlayout->addWidget(_frequencySpinBox);
+
+        vlayout->addItem(hlayout);
+
+
+        _timer = new QTimer(this);
+        _updateTimerFrequency();
+        _frequencySpinBox->setEnabled(_autoCheckBox->isChecked());
+
+
+        // Private connections.
+        connect(_autoCheckBox, &QCheckBox::toggled, this, &This::_toggleAutoUpdates);
+        connect(_frequencySpinBox,
+                &QDoubleSpinBox::editingFinished,
+                this,
+                &This::_updateTimerFrequency);
+
+        // Public connections.
+        connect(_updateButton, &QPushButton::pressed, this, &This::updateSingle);
+        connect(_timer, &QTimer::timeout, this, &This::updatePeriodic);
+
+        connect(this, &This::updateSingle, this, &This::update);
+        connect(this, &This::updatePeriodic, this, &This::update);
+
+        // See `startTimerIfEnabled` for the signal reasoning.
+        // qOverload is required because `QTimer::start()` is overloaded.
+        connect(this, &This::startTimerSignal, _timer, qOverload<>(&QTimer::start));
+        connect(this, &This::stopTimerSignal, _timer, &QTimer::stop);
+    }
+
+    QPushButton*
+    PeriodicUpdateWidget::updateButton()
+    {
+        return _updateButton;
+    }
+
+    int
+    PeriodicUpdateWidget::getUpdateIntervalMs() const
+    {
+        return static_cast<int>(std::round(1000 / _frequencySpinBox->value()));
+    }
+
+    void
+    PeriodicUpdateWidget::startTimerIfEnabled()
+    {
+        /* A QTimer can only be started and stopped within its own thread (the thread for which
+         * it has the greatest affinity). Since this method can be called from any thread, we
+         * need to take a detour through these signals, which can be emitted from any thread and
+         * will always be caught in this object's (and thus the timer's) native thread.
+         */
+        if (_autoCheckBox->isChecked())
+        {
+            emit startTimerSignal();
+        }
+        else
+        {
+            emit stopTimerSignal();
+        }
+    }
+
+    void
+    PeriodicUpdateWidget::stopTimer()
+    {
+        // See `startTimerIfEnabled` for the signal reasoning.
+        emit stopTimerSignal();
+    }
+
+    void
+    PeriodicUpdateWidget::_updateTimerFrequency()
+    {
+        _timer->setInterval(getUpdateIntervalMs());
+    }
+
+    void
+    PeriodicUpdateWidget::_toggleAutoUpdates(bool enabled)
+    {
+        // This method is already a slot, so it doesn't need to use the timer signals.
+        _frequencySpinBox->setEnabled(enabled);
+        if (enabled)
+        {
+            _timer->start();
+        }
+        else
+        {
+            _timer->stop();
+        }
+    }
+
+    QCheckBox*
+    PeriodicUpdateWidget::autoCheckBox()
+    {
+        return _autoCheckBox;
+    }
+
+    QDoubleSpinBox*
+    PeriodicUpdateWidget::frequencySpinBox()
+    {
+        return _frequencySpinBox;
+    }
+
+    QTimer*
+    PeriodicUpdateWidget::timer()
+    {
+        return _timer;
+    }
+
+} // namespace armarx
diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.h b/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d199db39afc0a7042f5a5225b1f09c08d14bf46
--- /dev/null
+++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <QWidget>
+
+
+class QCheckBox;
+class QDoubleSpinBox;
+class QPushButton;
+class QTimer;
+
+namespace armarx
+{
+
+    class PeriodicUpdateWidget : public QWidget
+    {
+        Q_OBJECT
+        using This = PeriodicUpdateWidget;
+
+    public:
+        PeriodicUpdateWidget(double frequency = 2.0, double maxFrequency = 60);
+
+
+        QTimer* timer();
+
+        QCheckBox* autoCheckBox();
+        QDoubleSpinBox* frequencySpinBox();
+        QPushButton* updateButton();
+
+        bool isAutoEnabled() const;
+        double getUpdateFrequency() const;
+        int getUpdateIntervalMs() const;
+
+        void startTimerIfEnabled();
+        void stopTimer();
+
+
+    public slots:
+
+    signals:
+
+        void update();
+
+        void updateSingle();
+        void updatePeriodic();
+
+    private slots:
+
+        void _updateTimerFrequency();
+        void _toggleAutoUpdates(bool enabled);
+
+    signals:
+
+        void startTimerSignal();
+        void stopTimerSignal();
+
+    private:
+        QPushButton* _updateButton;
+        QCheckBox* _autoCheckBox;
+        QDoubleSpinBox* _frequencySpinBox;
+
+        QPushButton* _collapseAllButton;
+
+        QTimer* _timer;
+    };
+
+} // namespace armarx