diff --git a/src/UI/DockWidgetTitleBar.cc b/src/UI/DockWidgetTitleBar.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5fdc2719e1465da1d88ddec8c79553f88c7b84d3
--- /dev/null
+++ b/src/UI/DockWidgetTitleBar.cc
@@ -0,0 +1,68 @@
+#include "DockWidgetTitleBar.hh"
+
+#include <QLabel>
+#include <QHBoxLayout>
+#include <QPushButton>
+
+using namespace Vivy;
+
+DockWidgetTitleBar::DockWidgetTitleBar(QDockWidget *parent) noexcept
+    : QWidget(parent)
+    , attachedDock(parent)
+{
+    if (parent == nullptr)
+        qFatal("Can't pass a nullptr as a parent widget pointer");
+
+    auto *box = new QHBoxLayout(this);
+    box->addWidget(new QLabel(parent->windowTitle(), this));
+    qobject_cast<QHBoxLayout *>(layout())->setStretch(0, 1);
+}
+
+void
+DockWidgetTitleBar::handleFeaturesChanged(QDockWidget::DockWidgetFeatures feature) noexcept
+{
+    clearControls();
+
+    if (feature & QDockWidget::DockWidgetFloatable) {
+        QPushButton *const button = new QPushButton("float", this);
+        button->setEnabled(true);
+        connect(button, &QPushButton::clicked, attachedDock,
+                [this](bool) { attachedDock->setFloating(!attachedDock->isFloating()); });
+        layout()->addWidget(button);
+    }
+
+    if (feature & QDockWidget::DockWidgetClosable) {
+        QPushButton *const button = new QPushButton("close", this);
+        button->setEnabled(true);
+        connect(button, &QPushButton::clicked, attachedDock, [this](bool) {
+            QAction *const act = attachedDock->toggleViewAction();
+            Q_ASSERT(act->isCheckable());
+            act->trigger();
+        });
+        layout()->addWidget(button);
+    }
+}
+
+void
+DockWidgetTitleBar::clearControls() noexcept
+{
+    QHBoxLayout *const box = qobject_cast<QHBoxLayout *>(layout());
+    QLayoutItem *item      = nullptr;
+
+    for (int i = 1; i < box->count(); ++i) {
+        item = box->takeAt(i);
+        delete item->widget();
+        delete item;
+    }
+}
+
+void
+DockWidgetTitleBar::addToDock(QDockWidget *const dock) noexcept
+{
+    DockWidgetTitleBar *const titleBar = new DockWidgetTitleBar(dock);
+    Utils::setTransparentBackgroundForWidget(titleBar);
+    connect(dock, &QDockWidget::featuresChanged, titleBar,
+            &DockWidgetTitleBar::handleFeaturesChanged);
+    dock->setTitleBarWidget(titleBar);
+    titleBar->handleFeaturesChanged(dock->features());
+}
diff --git a/src/UI/DockWidgetTitleBar.hh b/src/UI/DockWidgetTitleBar.hh
new file mode 100644
index 0000000000000000000000000000000000000000..97c2ad6d8bffce7889b67c0e6ed068a3df02f03b
--- /dev/null
+++ b/src/UI/DockWidgetTitleBar.hh
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "Utils.hh"
+#include "../Lib/Utils.hh"
+
+class QDockWidget;
+
+namespace Vivy
+{
+class DockWidgetTitleBar final : public QWidget {
+    Q_OBJECT
+    VIVY_UNMOVABLE_OBJECT(DockWidgetTitleBar)
+
+    QDockWidget *attachedDock{ nullptr };
+    QDockWidget::DockWidgetFeature currentFeatures{ QDockWidget::NoDockWidgetFeatures };
+
+    explicit DockWidgetTitleBar(QDockWidget *parent) noexcept;
+    void handleFeaturesChanged(QDockWidget::DockWidgetFeatures) noexcept;
+    void clearControls() noexcept;
+    void addCloseButton() noexcept;
+    void addFloatButton() noexcept;
+
+public:
+    static void addToDock(QDockWidget *const) noexcept;
+};
+}
diff --git a/src/UI/VivyDocumentView.cc b/src/UI/VivyDocumentView.cc
index 0f36c52bfb19c9d89b96de7a350eba505b5ffe16..3bbf3a9e4cdec8fb2afc6ed1647fe92a663ac08d 100644
--- a/src/UI/VivyDocumentView.cc
+++ b/src/UI/VivyDocumentView.cc
@@ -1,5 +1,6 @@
 #include "VivyDocumentView.hh"
 #include "PropertyModel.hh"
+#include "DockWidgetTitleBar.hh"
 #include "Utils.hh"
 #include "DocumentViews/AudioVisualizer.hh"
 #include "DocumentViews/AssLinesView.hh"
@@ -92,8 +93,7 @@ VivyDocumentView::loadVideoView() noexcept
                                    QDockWidget::DockWidgetFloatable |
                                    QDockWidget::DockWidgetClosable);
             addDockWidget(Qt::BottomDockWidgetArea, videoView, Qt::Vertical);
-            videoView->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(videoView->titleBarWidget());
+            DockWidgetTitleBar::addToDock(videoView);
         }
 
         // Kubat: because the dock is "closable", when closed the widget itself
@@ -115,8 +115,7 @@ VivyDocumentView::loadAssView() noexcept
             assLines->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea |
                                       Qt::BottomDockWidgetArea);
             addDockWidget(Qt::BottomDockWidgetArea, assLines, Qt::Vertical);
-            assLines->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(assLines->titleBarWidget());
+            DockWidgetTitleBar::addToDock(assLines);
         }
 
         assModel.reset(new AssLinesModel(document->getAssSubDocument()->getLines()));
@@ -147,8 +146,7 @@ VivyDocumentView::loadAudioView() noexcept
             visualizer->setFeatures(QDockWidget::DockWidgetMovable |
                                     QDockWidget::DockWidgetClosable);
             addDockWidget(Qt::LeftDockWidgetArea, visualizer, Qt::Horizontal);
-            visualizer->setTitleBarWidget(new QWidget(this));
-            Utils::setTransparentBackgroundForWidget(visualizer->titleBarWidget());
+            DockWidgetTitleBar::addToDock(visualizer);
         }
 
         // Kubat: don't check, may throw an error but don't think we can
@@ -196,8 +194,7 @@ VivyDocumentView::openProperties() noexcept
         property = new QDockWidget("Properties", this);
         property->setAllowedAreas(Qt::AllDockWidgetAreas);
         addDockWidget(Qt::RightDockWidgetArea, property, Qt::Vertical);
-        property->setTitleBarWidget(new QWidget(this));
-        Utils::setTransparentBackgroundForWidget(property->titleBarWidget());
+        DockWidgetTitleBar::addToDock(property);
     }
 
     property->setWidget(view);